From fbd71e9e6aea22f435198d14ed90bba5aba75440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Tue, 25 Mar 2025 18:30:24 +0100 Subject: [PATCH 01/17] add caching as part of compilation --- packages/babel-compiler/babel-compiler.js | 69 ++++++++++++++++------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index f8d6217d54..f9ea38f32f 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -1,6 +1,8 @@ var semver = Npm.require("semver"); var JSON5 = Npm.require("json5"); var SWC = Npm.require("@swc/core"); +var fs = Npm.require('fs'); +var path = Npm.require('path'); /** * A compiler that can be instantiated with features and used inside @@ -51,7 +53,9 @@ BCp.processFilesForTarget = function (inputFiles) { // Returns an object suitable for passing to inputFile.addJavaScript, or // null to indicate there was an error, and nothing should be added. BCp.processOneFileForTarget = function (inputFile, source) { + var self = this; // capture context this._babelrcCache = this._babelrcCache || Object.create(null); + this._transformCache = this._transformCache || Object.create(null); if (typeof source !== "string") { // Other compiler plugins can call processOneFileForTarget with a @@ -122,23 +126,18 @@ BCp.processOneFileForTarget = function (inputFile, source) { }, }; - this.inferTypeScriptConfig( - features, inputFile, cacheOptions.cacheDeps); + this.inferTypeScriptConfig(features, inputFile, cacheOptions.cacheDeps); var babelOptions = Babel.getDefaultOptions(features); babelOptions.caller = { name: "meteor", arch }; - this.inferExtraBabelOptions( - inputFile, - babelOptions, - cacheOptions.cacheDeps - ); + this.inferExtraBabelOptions(inputFile, babelOptions, cacheOptions.cacheDeps); babelOptions.sourceMaps = true; babelOptions.filename = babelOptions.sourceFileName = packageName - ? "packages/" + packageName + "/" + inputFilePath - : inputFilePath; + ? "packages/" + packageName + "/" + inputFilePath + : inputFilePath; if (this.modifyBabelConfig) { this.modifyBabelConfig(babelOptions, inputFile); @@ -146,19 +145,38 @@ BCp.processOneFileForTarget = function (inputFile, source) { try { var result = profile('Babel.compile', function () { - let compilation; + // Determine if SWC should be used based on package and file criteria. + const packagesSkipSwc = []; + const fileSkipSwc = ['webapp_server.js']; // top level await + const shouldUseSwc = !packagesSkipSwc.includes(packageName) && + !fileSkipSwc.includes(inputFilePath); + + // Create a cache key based on the source hash and the compiler used. + const cacheKey = `${toBeAdded.hash}-${shouldUseSwc ? 'swc' : 'babel'}`; + + // Check RAM cache + let compilation = self._transformCache[cacheKey]; + // Check file system cache if enabled + if (!compilation && self.cacheDirectory) { + const cacheFilePath = path.join(self.cacheDirectory, 'compilation-cache', cacheKey + '.json'); + if (fs.existsSync(cacheFilePath)) { + try { + compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); + self._transformCache[cacheKey] = compilation; + } catch (e) { + // If reading/parsing the cache fails, ignore and continue. + } + } + } + // Return cached result if found. + if (compilation) { + return compilation; + } + + // Perform compilation try { - const packagesSkipSwc = []; - const fileSkipSwc = ['webapp_server.js']; // top level await - - // Determine if SWC should be used based on package and file criteria. - const shouldUseSwc = - !packagesSkipSwc.includes(packageName) && - !fileSkipSwc.includes(inputFilePath); - if (shouldUseSwc) { - const isTypescriptSyntax = - inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); + const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); const hasTSXSupport = inputFilePath.endsWith('.tsx'); const hasJSXSupport = inputFilePath.endsWith('.jsx'); @@ -190,6 +208,17 @@ BCp.processOneFileForTarget = function (inputFile, source) { compilation = Babel.compile(source, babelOptions, cacheOptions); } + // Save result in caches + self._transformCache[cacheKey] = compilation; + if (self.cacheDirectory) { + const cacheFilePath = path.join(self.cacheDirectory, 'compilation-cache', cacheKey + '.json'); + try { + fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }); + fs.writeFileSync(cacheFilePath, JSON.stringify(compilation), 'utf8'); + } catch (e) { + // If file caching fails, ignore the error. + } + } return compilation; }); } catch (e) { From 49a639955bd27ad719148a4e1f1a039ac8ef6c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 26 Mar 2025 15:08:58 +0100 Subject: [PATCH 02/17] apply cache only for swc as meteor-babel already describes a cache for babel --- packages/babel-compiler/babel-compiler.js | 73 ++++++++++++----------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index f9ea38f32f..9d928d6bd8 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -55,7 +55,7 @@ BCp.processFilesForTarget = function (inputFiles) { BCp.processOneFileForTarget = function (inputFile, source) { var self = this; // capture context this._babelrcCache = this._babelrcCache || Object.create(null); - this._transformCache = this._transformCache || Object.create(null); + this._swcCache = this._swcCache || Object.create(null); if (typeof source !== "string") { // Other compiler plugins can call processOneFileForTarget with a @@ -151,35 +151,38 @@ BCp.processOneFileForTarget = function (inputFile, source) { const shouldUseSwc = !packagesSkipSwc.includes(packageName) && !fileSkipSwc.includes(inputFilePath); - // Create a cache key based on the source hash and the compiler used. - const cacheKey = `${toBeAdded.hash}-${shouldUseSwc ? 'swc' : 'babel'}`; - // Check RAM cache - let compilation = self._transformCache[cacheKey]; - // Check file system cache if enabled - if (!compilation && self.cacheDirectory) { - const cacheFilePath = path.join(self.cacheDirectory, 'compilation-cache', cacheKey + '.json'); - if (fs.existsSync(cacheFilePath)) { - try { - compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); - self._transformCache[cacheKey] = compilation; - } catch (e) { - // If reading/parsing the cache fails, ignore and continue. - } - } - } - // Return cached result if found. - if (compilation) { - return compilation; - } - - // Perform compilation + let compilation; try { if (shouldUseSwc) { + // Create a cache key based on the source hash and the compiler used. + const cacheKey = toBeAdded.hash; + const cacheContext = 'swc-cache'; + + // Check RAM cache + compilation = self._swcCache[cacheKey]; + // Check file system cache if enabled + if (!compilation && self.cacheDirectory) { + const cacheFilePath = path.join(self.cacheDirectory, cacheContext, cacheKey + '.json'); + if (fs.existsSync(cacheFilePath)) { + try { + compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); + self._swcCache[cacheKey] = compilation; + } catch (e) { + // If reading/parsing the cache fails, ignore and continue. + } + } + } + // Return cached result if found. + if (compilation) { + return compilation; + } + const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); const hasTSXSupport = inputFilePath.endsWith('.tsx'); const hasJSXSupport = inputFilePath.endsWith('.jsx'); + // Perform compilation const transformed = SWC.transformSync(source, { jsc: { target: 'es2015', @@ -200,6 +203,18 @@ BCp.processOneFileForTarget = function (inputFile, source) { hash: toBeAdded.hash, sourceType: 'module', }; + + // Save result in cache + self._swcCache[cacheKey] = compilation; + if (self.cacheDirectory) { + const cacheFilePath = path.join(self.cacheDirectory, cacheContext, cacheKey + '.json'); + try { + fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }); + fs.writeFileSync(cacheFilePath, JSON.stringify(compilation), 'utf8'); + } catch (e) { + // If file caching fails, ignore the error. + } + } } else { compilation = Babel.compile(source, babelOptions, cacheOptions); } @@ -207,18 +222,6 @@ BCp.processOneFileForTarget = function (inputFile, source) { // If SWC fails, fall back to Babel compilation = Babel.compile(source, babelOptions, cacheOptions); } - - // Save result in caches - self._transformCache[cacheKey] = compilation; - if (self.cacheDirectory) { - const cacheFilePath = path.join(self.cacheDirectory, 'compilation-cache', cacheKey + '.json'); - try { - fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }); - fs.writeFileSync(cacheFilePath, JSON.stringify(compilation), 'utf8'); - } catch (e) { - // If file caching fails, ignore the error. - } - } return compilation; }); } catch (e) { From c4e8fd4c4e628d82fae0b8e26b8db089dcde32bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 26 Mar 2025 15:19:50 +0100 Subject: [PATCH 03/17] better cache context --- packages/babel-compiler/babel-compiler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 9d928d6bd8..09d680c19e 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -157,7 +157,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { if (shouldUseSwc) { // Create a cache key based on the source hash and the compiler used. const cacheKey = toBeAdded.hash; - const cacheContext = 'swc-cache'; + const cacheContext = '.swc-cache'; // Check RAM cache compilation = self._swcCache[cacheKey]; From c77611f57c63ea1bde03c618677234def26b48c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 26 Mar 2025 15:51:14 +0100 Subject: [PATCH 04/17] async write the cache --- packages/babel-compiler/babel-compiler.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 09d680c19e..8181009167 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -209,8 +209,12 @@ BCp.processOneFileForTarget = function (inputFile, source) { if (self.cacheDirectory) { const cacheFilePath = path.join(self.cacheDirectory, cacheContext, cacheKey + '.json'); try { - fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }); - fs.writeFileSync(cacheFilePath, JSON.stringify(compilation), 'utf8'); + const writeFileCache = async () => { + await fs.promises.mkdir(path.dirname(cacheFilePath), { recursive: true }); + await fs.promises.writeFile(cacheFilePath, JSON.stringify(compilation), 'utf8'); + }; + // Asynchronously write the cache without blocking + writeFileCache(); } catch (e) { // If file caching fails, ignore the error. } From e1e44c15cb8a08078e951def3ae9c44de1fa540b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 27 Mar 2025 16:45:11 +0100 Subject: [PATCH 05/17] perserve Meteor's specifics: reify modules, nested imports and top-level await support --- packages/babel-compiler/babel-compiler.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index f8d6217d54..1de5a197d8 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -1,6 +1,8 @@ var semver = Npm.require("semver"); var JSON5 = Npm.require("json5"); var SWC = Npm.require("@swc/core"); +const reifyCompile = Npm.require("@meteorjs/reify/lib/compiler").compile; +const reifyAcornTopLevelParse = Npm.require("@meteorjs/reify/lib/parsers/top-level").parse; /** * A compiler that can be instantiated with features and used inside @@ -149,7 +151,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { let compilation; try { const packagesSkipSwc = []; - const fileSkipSwc = ['webapp_server.js']; // top level await + const fileSkipSwc = []; // top level await // Determine if SWC should be used based on package and file criteria. const shouldUseSwc = @@ -171,13 +173,25 @@ BCp.processOneFileForTarget = function (inputFile, source) { tsx: hasTSXSupport, }, }, - module: { type: 'commonjs' }, + module: { type: 'es6' }, minify: false, sourceMaps: true, }); + let content = transformed.code; + // Perserve Meteor's specifics: reify modules, nested imports and top-level await support. + const result = reifyCompile(content, { + parse: reifyAcornTopLevelParse, + generateLetDeclarations: false, + ast: false, + }); + if (!result.identical) { + identical = false; + content = result.code; + } + compilation = { - code: transformed.code, + code: content, map: JSON.parse(transformed.map), hash: toBeAdded.hash, sourceType: 'module', From 2ddc37e2123cb7dae5e362579d8adfc1a5ee01ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 27 Mar 2025 17:13:08 +0100 Subject: [PATCH 06/17] revert removal of nested imports as they are still compatible --- packages/autoupdate/autoupdate_server.js | 2 +- packages/ddp-client/common/livedata_connection.js | 3 ++- packages/ecmascript/runtime-tests.js | 4 ++-- packages/ejson/ejson.js | 2 +- packages/mongo/collection/methods_index.js | 6 ++++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index 82af4c65e6..7d20443c27 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -25,7 +25,6 @@ // The ID of each document is the client architecture, and the fields of // the document are the versions described above. -import { onMessage } from "meteor/inter-process-messaging"; import { ClientVersions } from "./client_versions.js"; export const Autoupdate = __meteor_runtime_config__.autoupdate = { @@ -153,6 +152,7 @@ function enqueueVersionsRefresh() { const setupListeners = () => { // Listen for messages pertaining to the client-refresh topic. + import { onMessage } from "meteor/inter-process-messaging"; onMessage("client-refresh", enqueueVersionsRefresh); // Another way to tell the process to refresh: send SIGHUP signal diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index a568c7ca70..abe1161d21 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -4,7 +4,6 @@ import { Tracker } from 'meteor/tracker'; import { EJSON } from 'meteor/ejson'; import { Random } from 'meteor/random'; import { MongoID } from 'meteor/mongo-id'; -import { ClientStream } from "meteor/socket-stream-client"; import { DDP } from './namespace.js'; import { MethodInvoker } from './method_invoker'; import { @@ -75,6 +74,8 @@ export class Connection { if (typeof url === 'object') { self._stream = url; } else { + import { ClientStream } from "meteor/socket-stream-client"; + self._stream = new ClientStream(url, { retry: options.retry, ConnectionError: DDP.ConnectionError, diff --git a/packages/ecmascript/runtime-tests.js b/packages/ecmascript/runtime-tests.js index 693116b7e0..a276d7e380 100644 --- a/packages/ecmascript/runtime-tests.js +++ b/packages/ecmascript/runtime-tests.js @@ -1,5 +1,3 @@ -import { testExport as oyez } from './runtime-tests.js'; - const isNode8OrLater = Meteor.isServer && parseInt(process.versions.node) >= 8; Tinytest.add('ecmascript - runtime - template literals', test => { @@ -171,12 +169,14 @@ Tinytest.add('ecmascript - runtime - classes - properties', test => { static staticProp = 1234; check = self => { + import { testExport as oyez } from './runtime-tests.js'; test.equal(oyez, 'oyez'); test.isTrue(self === this); test.equal(this.property, 'property'); }; method() { + import { testExport as oyez } from './runtime-tests.js'; test.equal(oyez, 'oyez'); } } diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 76ef364651..e83d099279 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -9,7 +9,6 @@ import { isInfOrNaN, handleError, } from './utils'; -import canonicalStringify from './stringify'; /** * @namespace @@ -396,6 +395,7 @@ EJSON.stringify = handleError((item, options) => { let serialized; const json = EJSON.toJSONValue(item); if (options && (options.canonical || options.indent)) { + import canonicalStringify from './stringify'; serialized = canonicalStringify(json, options); } else { serialized = JSON.stringify(json); diff --git a/packages/mongo/collection/methods_index.js b/packages/mongo/collection/methods_index.js index 0731e7a5e1..c516d7522b 100644 --- a/packages/mongo/collection/methods_index.js +++ b/packages/mongo/collection/methods_index.js @@ -1,5 +1,3 @@ -import { Log } from 'meteor/logging'; - export const IndexMethods = { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. @@ -23,6 +21,8 @@ export const IndexMethods = { if (self._collection.createIndexAsync) { await self._collection.createIndexAsync(index, options); } else { + import { Log } from 'meteor/logging'; + Log.debug(`ensureIndexAsync has been deprecated, please use the new 'createIndexAsync' instead${ options?.name ? `, index name: ${ options.name }` : `, index: ${ JSON.stringify(index) }` }`) await self._collection.ensureIndexAsync(index, options); } @@ -54,6 +54,8 @@ export const IndexMethods = { ) && Meteor.settings?.packages?.mongo?.reCreateIndexOnOptionMismatch ) { + import { Log } from 'meteor/logging'; + Log.info(`Re-creating index ${ index } for ${ self._name } due to options mismatch.`); await self._collection.dropIndexAsync(index); await self._collection.createIndexAsync(index, options); From 8e8801e2ef08b7a876d4a93e14bf873a178cddc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 08:55:43 +0100 Subject: [PATCH 07/17] remove unrequired nested imports --- packages/ecmascript/runtime-tests.js | 4 ++-- packages/ejson/ejson.js | 2 +- packages/mongo/collection/methods_index.js | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/ecmascript/runtime-tests.js b/packages/ecmascript/runtime-tests.js index a276d7e380..693116b7e0 100644 --- a/packages/ecmascript/runtime-tests.js +++ b/packages/ecmascript/runtime-tests.js @@ -1,3 +1,5 @@ +import { testExport as oyez } from './runtime-tests.js'; + const isNode8OrLater = Meteor.isServer && parseInt(process.versions.node) >= 8; Tinytest.add('ecmascript - runtime - template literals', test => { @@ -169,14 +171,12 @@ Tinytest.add('ecmascript - runtime - classes - properties', test => { static staticProp = 1234; check = self => { - import { testExport as oyez } from './runtime-tests.js'; test.equal(oyez, 'oyez'); test.isTrue(self === this); test.equal(this.property, 'property'); }; method() { - import { testExport as oyez } from './runtime-tests.js'; test.equal(oyez, 'oyez'); } } diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index e83d099279..76ef364651 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -9,6 +9,7 @@ import { isInfOrNaN, handleError, } from './utils'; +import canonicalStringify from './stringify'; /** * @namespace @@ -395,7 +396,6 @@ EJSON.stringify = handleError((item, options) => { let serialized; const json = EJSON.toJSONValue(item); if (options && (options.canonical || options.indent)) { - import canonicalStringify from './stringify'; serialized = canonicalStringify(json, options); } else { serialized = JSON.stringify(json); diff --git a/packages/mongo/collection/methods_index.js b/packages/mongo/collection/methods_index.js index c516d7522b..0731e7a5e1 100644 --- a/packages/mongo/collection/methods_index.js +++ b/packages/mongo/collection/methods_index.js @@ -1,3 +1,5 @@ +import { Log } from 'meteor/logging'; + export const IndexMethods = { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. @@ -21,8 +23,6 @@ export const IndexMethods = { if (self._collection.createIndexAsync) { await self._collection.createIndexAsync(index, options); } else { - import { Log } from 'meteor/logging'; - Log.debug(`ensureIndexAsync has been deprecated, please use the new 'createIndexAsync' instead${ options?.name ? `, index name: ${ options.name }` : `, index: ${ JSON.stringify(index) }` }`) await self._collection.ensureIndexAsync(index, options); } @@ -54,8 +54,6 @@ export const IndexMethods = { ) && Meteor.settings?.packages?.mongo?.reCreateIndexOnOptionMismatch ) { - import { Log } from 'meteor/logging'; - Log.info(`Re-creating index ${ index } for ${ self._name } due to options mismatch.`); await self._collection.dropIndexAsync(index); await self._collection.createIndexAsync(index, options); From 74bdbd8d00f58995c0f606bdca1f3aa796f0074a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 09:12:02 +0100 Subject: [PATCH 08/17] remove unrequired nested imports --- packages/autoupdate/autoupdate_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index 7d20443c27..82af4c65e6 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -25,6 +25,7 @@ // The ID of each document is the client architecture, and the fields of // the document are the versions described above. +import { onMessage } from "meteor/inter-process-messaging"; import { ClientVersions } from "./client_versions.js"; export const Autoupdate = __meteor_runtime_config__.autoupdate = { @@ -152,7 +153,6 @@ function enqueueVersionsRefresh() { const setupListeners = () => { // Listen for messages pertaining to the client-refresh topic. - import { onMessage } from "meteor/inter-process-messaging"; onMessage("client-refresh", enqueueVersionsRefresh); // Another way to tell the process to refresh: send SIGHUP signal From a3b12b2eb611f1de3cedef929fe1d4238158486e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 09:26:53 +0100 Subject: [PATCH 09/17] prefer require than nested import --- packages/ddp-client/common/livedata_connection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index abe1161d21..9755a8012a 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -74,7 +74,7 @@ export class Connection { if (typeof url === 'object') { self._stream = url; } else { - import { ClientStream } from "meteor/socket-stream-client"; + const { ClientStream } = require("meteor/socket-stream-client"); self._stream = new ClientStream(url, { retry: options.retry, From cf6dc9ca5a34d4f961225294c10391e2e4405dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 09:32:40 +0100 Subject: [PATCH 10/17] ensure incompatible files will directly parse with babel once detected --- packages/babel-compiler/babel-compiler.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 8cf2c4245e..77271959d4 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -58,6 +58,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { var self = this; // capture context this._babelrcCache = this._babelrcCache || Object.create(null); this._swcCache = this._swcCache || Object.create(null); + this._swcIncompatible = this._swcIncompatible || Object.create(null); if (typeof source !== "string") { // Other compiler plugins can call processOneFileForTarget with a @@ -150,8 +151,10 @@ BCp.processOneFileForTarget = function (inputFile, source) { // Determine if SWC should be used based on package and file criteria. const packagesSkipSwc = []; const fileSkipSwc = []; // top level await - const shouldUseSwc = !packagesSkipSwc.includes(packageName) && - !fileSkipSwc.includes(inputFilePath); + const shouldUseSwc = + !packagesSkipSwc.includes(packageName) && + !fileSkipSwc.includes(inputFilePath) && + !self._swcIncompatible[toBeAdded.hash]; // Check RAM cache let compilation; @@ -237,6 +240,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { compilation = Babel.compile(source, babelOptions, cacheOptions); } } catch (e) { + self._swcIncompatible[toBeAdded.hash] = true; // If SWC fails, fall back to Babel compilation = Babel.compile(source, babelOptions, cacheOptions); } From 42b61a4ecc6f50e8d7160314a231967b2fa7394f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 10:35:23 +0100 Subject: [PATCH 11/17] use latest acorn parser and proper reify options --- packages/babel-compiler/babel-compiler.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 77271959d4..d5f0263801 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -2,7 +2,7 @@ var semver = Npm.require("semver"); var JSON5 = Npm.require("json5"); var SWC = Npm.require("@swc/core"); const reifyCompile = Npm.require("@meteorjs/reify/lib/compiler").compile; -const reifyAcornTopLevelParse = Npm.require("@meteorjs/reify/lib/parsers/top-level").parse; +const reifyAcornParse = Npm.require("@meteorjs/reify/lib/parsers/acorn").parse; var fs = Npm.require('fs'); var path = Npm.require('path'); @@ -205,9 +205,21 @@ BCp.processOneFileForTarget = function (inputFile, source) { let content = transformed.code; // Perserve Meteor's specifics: reify modules, nested imports and top-level await support. const result = reifyCompile(content, { - parse: reifyAcornTopLevelParse, + parse: reifyAcornParse, generateLetDeclarations: false, ast: false, + // Enforce reify options for proper compatibility (TODO export getReifyOptions) + // https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-babel/options.js#L19 + avoidModernSyntax: true, + enforceStrictMode: false, + dynamicImport: true, + ...features.topLevelAwait && { topLevelAwait: true }, + ...features.compileForShell && { moduleAlias: 'module' }, + ...(features.modernBrowsers || + features.nodeMajorVersion >= 8) && { + avoidModernSyntax: false, + generateLetDeclarations: true, + }, }); if (!result.identical) { identical = false; From 1d11de08d7afc39cd305f8234809f7d52788ccfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 12:24:21 +0100 Subject: [PATCH 12/17] use reify acorn parser on static-assets server runtime --- tools/static-assets/server/runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/static-assets/server/runtime.js b/tools/static-assets/server/runtime.js index 2b0f800940..9a1c6b15ef 100644 --- a/tools/static-assets/server/runtime.js +++ b/tools/static-assets/server/runtime.js @@ -47,7 +47,7 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { }; const reifyVersion = require("@meteorjs/reify/package.json").version; - const reifyBabelParse = require("@meteorjs/reify/lib/parsers/babel").parse; + const reifyAcornParse = require("@meteorjs/reify/lib/parsers/acorn").parse; const reifyCompile = require("@meteorjs/reify/lib/compiler").compile; function compileContent (content) { @@ -55,7 +55,7 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { try { const result = reifyCompile(content, { - parse: reifyBabelParse, + parse: reifyAcornParse, generateLetDeclarations: false, ast: false, }); From 94dd07b2d0826e9bf6ab6c3c083b4774641614ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 28 Mar 2025 12:27:39 +0100 Subject: [PATCH 13/17] use reify acorn parser on import scanner --- tools/isobuild/import-scanner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/isobuild/import-scanner.ts b/tools/isobuild/import-scanner.ts index acec76b398..e05900b2f2 100644 --- a/tools/isobuild/import-scanner.ts +++ b/tools/isobuild/import-scanner.ts @@ -45,7 +45,7 @@ import { import { wrap } from "optimism"; const { compile: reifyCompile } = require("@meteorjs/reify/lib/compiler"); -const { parse: reifyBabelParse } = require("@meteorjs/reify/lib/parsers/babel"); +const { parse: reifyAcornParse } = require("@meteorjs/reify/lib/parsers/acorn"); import Resolver, { Resolution } from "./resolver"; import LRUCache from 'lru-cache'; @@ -88,7 +88,7 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function ( const isLegacy = isLegacyArch(bundleArch); let result = reifyCompile(stripHashBang(source), { - parse: reifyBabelParse, + parse: reifyAcornParse, generateLetDeclarations: !isLegacy, avoidModernSyntax: isLegacy, enforceStrictMode: false, From ee23cb4e575a2765da82f602d47185b55b5f62bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Tue, 1 Apr 2025 19:38:58 +0200 Subject: [PATCH 14/17] ensure SWC.compile profile context and address feedback --- packages/babel-compiler/babel-compiler.js | 142 ++++++++++++---------- 1 file changed, 78 insertions(+), 64 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index d5f0263801..d5e1f7437a 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -29,6 +29,67 @@ var isMeteorPre144 = semver.lt(process.version, "4.8.1"); var enableClientTLA = process.env.METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT === 'true'; +function compileWithBabel(source, babelOptions, cacheOptions) { + return profile('Babel.compile', function () { + return Babel.compile(source, babelOptions, cacheOptions); + }); +} + +function compileWithSWC(source, { inputFilePath, hash, features, ...swcOptions }) { + return profile('SWC.compile', function () { + // Determine file extension based syntax. + const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); + const hasTSXSupport = inputFilePath.endsWith('.tsx'); + const hasJSXSupport = inputFilePath.endsWith('.jsx'); + + // Perform SWC transformation. + const transformed = SWC.transformSync(source, { + ...swcOptions, + jsc: { + target: 'es2015', + parser: { + syntax: isTypescriptSyntax ? 'typescript' : 'ecmascript', + jsx: hasJSXSupport, + tsx: hasTSXSupport, + }, + }, + module: { type: 'es6' }, + minify: false, + sourceMaps: true, + }); + + let content = transformed.code; + + // Preserve Meteor-specific features: reify modules, nested imports, and top-level await support. + const result = reifyCompile(content, { + parse: reifyAcornParse, + generateLetDeclarations: false, + ast: false, + // Enforce reify options for proper compatibility. + avoidModernSyntax: true, + enforceStrictMode: false, + dynamicImport: true, + ...features.topLevelAwait && { topLevelAwait: true }, + ...features.compileForShell && { moduleAlias: 'module' }, + ...(features.modernBrowsers || features.nodeMajorVersion >= 8) && { + avoidModernSyntax: false, + generateLetDeclarations: true, + }, + }); + if (!result.identical) { + content = result.code; + } + + return { + code: content, + map: JSON.parse(transformed.map), + hash, + sourceType: 'module', + }; + }); +} + + BCp.processFilesForTarget = function (inputFiles) { var compiler = this; @@ -147,16 +208,16 @@ BCp.processOneFileForTarget = function (inputFile, source) { } try { - var result = profile('Babel.compile', function () { - // Determine if SWC should be used based on package and file criteria. + var result = (function getTranspilerCompilation() { const packagesSkipSwc = []; const fileSkipSwc = []; // top level await + + // Determine if SWC should be used based on package and file criteria. const shouldUseSwc = !packagesSkipSwc.includes(packageName) && !fileSkipSwc.includes(inputFilePath) && - !self._swcIncompatible[toBeAdded.hash]; + !this._swcIncompatible[toBeAdded.hash]; - // Check RAM cache let compilation; try { if (shouldUseSwc) { @@ -165,14 +226,14 @@ BCp.processOneFileForTarget = function (inputFile, source) { const cacheContext = '.swc-cache'; // Check RAM cache - compilation = self._swcCache[cacheKey]; + compilation = this._swcCache[cacheKey]; // Check file system cache if enabled - if (!compilation && self.cacheDirectory) { - const cacheFilePath = path.join(self.cacheDirectory, cacheContext, cacheKey + '.json'); + if (!compilation && this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, cacheContext, cacheKey + '.json'); if (fs.existsSync(cacheFilePath)) { try { compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); - self._swcCache[cacheKey] = compilation; + this._swcCache[cacheKey] = compilation; } catch (e) { // If reading/parsing the cache fails, ignore and continue. } @@ -183,60 +244,12 @@ BCp.processOneFileForTarget = function (inputFile, source) { return compilation; } - const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); - const hasTSXSupport = inputFilePath.endsWith('.tsx'); - const hasJSXSupport = inputFilePath.endsWith('.jsx'); - - // Perform compilation - const transformed = SWC.transformSync(source, { - jsc: { - target: 'es2015', - parser: { - syntax: isTypescriptSyntax ? 'typescript' : 'ecmascript', - jsx: hasJSXSupport, - tsx: hasTSXSupport, - }, - }, - module: { type: 'es6' }, - minify: false, - sourceMaps: true, - }); - - let content = transformed.code; - // Perserve Meteor's specifics: reify modules, nested imports and top-level await support. - const result = reifyCompile(content, { - parse: reifyAcornParse, - generateLetDeclarations: false, - ast: false, - // Enforce reify options for proper compatibility (TODO export getReifyOptions) - // https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-babel/options.js#L19 - avoidModernSyntax: true, - enforceStrictMode: false, - dynamicImport: true, - ...features.topLevelAwait && { topLevelAwait: true }, - ...features.compileForShell && { moduleAlias: 'module' }, - ...(features.modernBrowsers || - features.nodeMajorVersion >= 8) && { - avoidModernSyntax: false, - generateLetDeclarations: true, - }, - }); - if (!result.identical) { - identical = false; - content = result.code; - } - - compilation = { - code: content, - map: JSON.parse(transformed.map), - hash: toBeAdded.hash, - sourceType: 'module', - }; + compilation = compileWithSWC(source, { inputFilePath, hash: toBeAdded.hash, features }); // Save result in cache - self._swcCache[cacheKey] = compilation; - if (self.cacheDirectory) { - const cacheFilePath = path.join(self.cacheDirectory, cacheContext, cacheKey + '.json'); + this._swcCache[cacheKey] = compilation; + if (this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, cacheContext, cacheKey + '.json'); try { const writeFileCache = async () => { await fs.promises.mkdir(path.dirname(cacheFilePath), { recursive: true }); @@ -249,15 +262,16 @@ BCp.processOneFileForTarget = function (inputFile, source) { } } } else { - compilation = Babel.compile(source, babelOptions, cacheOptions); + compilation = compileWithBabel(source, babelOptions, cacheOptions); } } catch (e) { - self._swcIncompatible[toBeAdded.hash] = true; + this._swcIncompatible[toBeAdded.hash] = true; // If SWC fails, fall back to Babel - compilation = Babel.compile(source, babelOptions, cacheOptions); + compilation = compileWithBabel(source, babelOptions, cacheOptions); } + return compilation; - }); + }).call(this); } catch (e) { if (e.loc) { // Error is from @babel/parser. From 04c71f1a5b90fa17878c4bc2c5cf19232db29175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Tue, 1 Apr 2025 19:40:25 +0200 Subject: [PATCH 15/17] prefer an arrow function --- packages/babel-compiler/babel-compiler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index d5e1f7437a..f4df7343c9 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -208,7 +208,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { } try { - var result = (function getTranspilerCompilation() { + var result = (() => { const packagesSkipSwc = []; const fileSkipSwc = []; // top level await @@ -271,7 +271,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { } return compilation; - }).call(this); + })(); } catch (e) { if (e.loc) { // Error is from @babel/parser. From d137aea93c4815f7b399f20d8a63e050b6bb14a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Tue, 1 Apr 2025 19:51:11 +0200 Subject: [PATCH 16/17] refactor cache code --- packages/babel-compiler/babel-compiler.js | 82 +++++++++++++---------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index f4df7343c9..54727ee938 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -35,7 +35,7 @@ function compileWithBabel(source, babelOptions, cacheOptions) { }); } -function compileWithSWC(source, { inputFilePath, hash, features, ...swcOptions }) { +function compileWithSwc(source, swcOptions, { inputFilePath, features }) { return profile('SWC.compile', function () { // Determine file extension based syntax. const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); @@ -83,7 +83,6 @@ function compileWithSWC(source, { inputFilePath, hash, features, ...swcOptions } return { code: content, map: JSON.parse(transformed.map), - hash, sourceType: 'module', }; }); @@ -221,46 +220,17 @@ BCp.processOneFileForTarget = function (inputFile, source) { let compilation; try { if (shouldUseSwc) { - // Create a cache key based on the source hash and the compiler used. + // Create a cache key based on the source hash and the compiler used const cacheKey = toBeAdded.hash; - const cacheContext = '.swc-cache'; - - // Check RAM cache - compilation = this._swcCache[cacheKey]; - // Check file system cache if enabled - if (!compilation && this.cacheDirectory) { - const cacheFilePath = path.join(this.cacheDirectory, cacheContext, cacheKey + '.json'); - if (fs.existsSync(cacheFilePath)) { - try { - compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); - this._swcCache[cacheKey] = compilation; - } catch (e) { - // If reading/parsing the cache fails, ignore and continue. - } - } - } + // Check cache + compilation = this.readFromSwcCache({ cacheKey }); // Return cached result if found. if (compilation) { return compilation; } - - compilation = compileWithSWC(source, { inputFilePath, hash: toBeAdded.hash, features }); - + compilation = compileWithSwc(source, {}, { inputFilePath, features }); // Save result in cache - this._swcCache[cacheKey] = compilation; - if (this.cacheDirectory) { - const cacheFilePath = path.join(this.cacheDirectory, cacheContext, cacheKey + '.json'); - try { - const writeFileCache = async () => { - await fs.promises.mkdir(path.dirname(cacheFilePath), { recursive: true }); - await fs.promises.writeFile(cacheFilePath, JSON.stringify(compilation), 'utf8'); - }; - // Asynchronously write the cache without blocking - writeFileCache(); - } catch (e) { - // If file caching fails, ignore the error. - } - } + this.writeToSwcCache({ cacheKey, compilation }); } else { compilation = compileWithBabel(source, babelOptions, cacheOptions); } @@ -689,3 +659,43 @@ function packageNameFromTopLevelModuleId(id) { } return parts[0]; } + +const SwcCacheContext = '.swc-cache'; + +BCp.readFromSwcCache = function({ cacheKey }) { + // Check in-memory cache. + let compilation = this._swcCache[cacheKey]; + // If not found, try file system cache if enabled. + if (!compilation && this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, SwcCacheContext, `${cacheKey}.json`); + if (fs.existsSync(cacheFilePath)) { + try { + compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); + // Save back to in-memory cache. + this._swcCache[cacheKey] = compilation; + } catch (err) { + // Ignore any errors reading/parsing the cache. + } + } + } + return compilation; +}; + +BCp.writeToSwcCache = function({ cacheKey, compilation }) { + // Save to in-memory cache. + this._swcCache[cacheKey] = compilation; + // If file system caching is enabled, write asynchronously. + if (this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, SwcCacheContext, `${cacheKey}.json`); + try { + const writeFileCache = async () => { + await fs.promises.mkdir(path.dirname(cacheFilePath), { recursive: true }); + await fs.promises.writeFile(cacheFilePath, JSON.stringify(compilation), 'utf8'); + }; + // Invoke without blocking the main flow. + writeFileCache(); + } catch (err) { + // If writing fails, ignore the error. + } + } +}; From b512040335ca918e27d611092a79dfaab428fc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Tue, 1 Apr 2025 19:52:35 +0200 Subject: [PATCH 17/17] remove unrequired self --- packages/babel-compiler/babel-compiler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 54727ee938..35e3588df7 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -115,7 +115,6 @@ BCp.processFilesForTarget = function (inputFiles) { // Returns an object suitable for passing to inputFile.addJavaScript, or // null to indicate there was an error, and nothing should be added. BCp.processOneFileForTarget = function (inputFile, source) { - var self = this; // capture context this._babelrcCache = this._babelrcCache || Object.create(null); this._swcCache = this._swcCache || Object.create(null); this._swcIncompatible = this._swcIncompatible || Object.create(null);