From 95877da5160ed1c4426c53c36f5468b5d8678da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 19 May 2025 17:04:00 +0200 Subject: [PATCH 1/5] support swc.config.js file for dynamic config --- packages/babel-compiler/babel-compiler.js | 63 ++++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index b0c3c96a98..bfc45c5758 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -5,6 +5,8 @@ const reifyCompile = Npm.require("@meteorjs/reify/lib/compiler").compile; const reifyAcornParse = Npm.require("@meteorjs/reify/lib/parsers/acorn").parse; var fs = Npm.require('fs'); var path = Npm.require('path'); +var vm = Npm.require('vm'); +var crypto = Npm.require('crypto'); /** * A compiler that can be instantiated with features and used inside @@ -145,15 +147,37 @@ BCp.initializeMeteorAppConfig = function () { let lastModifiedSwcConfig; let lastModifiedSwcConfigTime; BCp.initializeMeteorAppSwcrc = function () { - if (!lastModifiedSwcConfig && !fs.existsSync(`${getMeteorAppDir()}/.swcrc`)) { + const hasSwcRc = fs.existsSync(`${getMeteorAppDir()}/.swcrc`); + const hasSwcJs = !hasSwcRc && fs.existsSync(`${getMeteorAppDir()}/swc.config.js`); + if (!lastModifiedSwcConfig && !hasSwcRc && !hasSwcJs) { return; } - const currentLastModifiedConfigTime = fs - .statSync(`${getMeteorAppDir()}/.swcrc`) - ?.mtime?.getTime(); + const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc'; + const filePath = `${getMeteorAppDir()}/${swcFile}`; + const fileStats = fs.statSync(filePath); + const fileModTime = fileStats?.mtime?.getTime(); + + let currentLastModifiedConfigTime; + if (hasSwcJs) { + // For dynamic JS files, first get the resolved configuration + const resolvedConfig = lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile); + // Calculate a hash of the resolved configuration to detect changes + const contentHash = crypto + .createHash('sha256') + .update(JSON.stringify(resolvedConfig)) + .digest('hex'); + // Combine file modification time and content hash to create a unique identifier + currentLastModifiedConfigTime = `${fileModTime}-${contentHash}`; + // Store the resolved configuration + lastModifiedSwcConfig = resolvedConfig; + } else { + // For static JSON files, just use the file modification time + currentLastModifiedConfigTime = fileModTime; + } + if (currentLastModifiedConfigTime !== lastModifiedSwcConfigTime) { lastModifiedSwcConfigTime = currentLastModifiedConfigTime; - lastModifiedSwcConfig = getMeteorAppSwcrc(); + lastModifiedSwcConfig = getMeteorAppSwcrc(swcFile); // Resolve custom baseUrl to an absolute path pointing to the project root if (lastModifiedSwcConfig.jsc && lastModifiedSwcConfig.jsc.baseUrl) { @@ -879,11 +903,34 @@ function getMeteorAppPackageJson() { ); } -function getMeteorAppSwcrc() { +function getMeteorAppSwcrc(file = '.swcrc') { try { - return JSON.parse(fs.readFileSync(`${getMeteorAppDir()}/.swcrc`, 'utf-8')); + const filePath = `${getMeteorAppDir()}/${file}`; + if (file.endsWith('.js')) { + let content = fs.readFileSync(filePath, 'utf-8'); + // Check if the content uses ES module syntax (export default) + if (content.includes('export default')) { + // Transform ES module syntax to CommonJS + content = content.replace(/export\s+default\s+/, 'module.exports = '); + } + const script = new vm.Script(` + (function() { + const module = {}; + module.exports = {}; + (function(exports, module) { + ${content} + })(module.exports, module); + return module.exports; + })() + `); + const context = vm.createContext({ process }); + return script.runInContext(context); + } else { + // For .swcrc and other JSON files, parse as JSON + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } } catch (e) { - console.error('Error parsing .swcrc file', e); + console.error(`Error parsing ${file} file`, e); } } From 5cdc7fa83be76d2be83c68d5cee3287a4264225f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 19 May 2025 17:13:08 +0200 Subject: [PATCH 2/5] update docs to add swc.config.js support --- .../modern-build-stack/transpiler-swc.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md index 7b67de62ab..9813a65540 100644 --- a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md +++ b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md @@ -121,6 +121,10 @@ You can also configure other options using the `.swcrc` format. One common case This overrides Meteor's internal SWC config to apply your settings, ensuring SWC processes `.js` or `.ts` files with React components without falling back to Babel. +Use `swc.config.js` in your project root for dynamic configuration. Meteor will import and apply the SWC config automatically. This lets you choose a config based on environment variables or other runtime factors. + +Explore additional custom SWC configs, including ["Import Aliases"](#import-aliases) and ["React Runtime"](#react-runtime). + ## Config API - `modern.transpiler: [true|false]` - Default: `true` @@ -190,6 +194,27 @@ To use the same aliases in SWC, add them to your [.swcrc](#custom-swcrc): } ``` +You can use `swc.config.js` to define different aliases based on an environment variable. + +``` js +var mode = process.env.MODE_ENV; + +var userAliases = { + "@user/*": ["user/*"], +}; + +var adminAliases = { + "@admin/*": ["admin/*"], +}; + +module.exports = { + jsc: { + baseUrl: "./", + paths: mode === "USER" ? uiAliases : adminAliases, + }, +}; +``` + This enables you to use `@ui/components` instead of `./ui/components` in your imports. ### React Runtime From a08e62ebbd632fa952154c7787ebd1350f898168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 19 May 2025 17:30:44 +0200 Subject: [PATCH 3/5] update docs to add swc.config.js support --- v3-docs/docs/about/modern-build-stack/transpiler-swc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md index 9813a65540..01bac2a63e 100644 --- a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md +++ b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md @@ -200,11 +200,11 @@ You can use `swc.config.js` to define different aliases based on an environment var mode = process.env.MODE_ENV; var userAliases = { - "@user/*": ["user/*"], + "@ui/*": ["user/*"], }; var adminAliases = { - "@admin/*": ["admin/*"], + "@ui/*": ["admin/*"], }; module.exports = { From 144a0360457964f15c7dbdb7bf7d647a74307205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 19 May 2025 17:30:55 +0200 Subject: [PATCH 4/5] update docs to add swc.config.js support --- v3-docs/docs/about/modern-build-stack/transpiler-swc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md index 01bac2a63e..a4b150a522 100644 --- a/v3-docs/docs/about/modern-build-stack/transpiler-swc.md +++ b/v3-docs/docs/about/modern-build-stack/transpiler-swc.md @@ -210,7 +210,7 @@ var adminAliases = { module.exports = { jsc: { baseUrl: "./", - paths: mode === "USER" ? uiAliases : adminAliases, + paths: mode === "USER" ? userAliases : adminAliases, }, }; ``` From 7bb6273eb59cc1461571df0a9c5105b14e53afaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 19 May 2025 17:54:17 +0200 Subject: [PATCH 5/5] test coverage for swc.config.js --- tools/tests/modern.js | 56 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/tools/tests/modern.js b/tools/tests/modern.js index cf40089a42..643ac2e5ea 100644 --- a/tools/tests/modern.js +++ b/tools/tests/modern.js @@ -59,7 +59,7 @@ selftest.define("modern build stack", async function () { await s.cd("modern"); s.set("METEOR_PROFILE", "0"); - + await writeModernConfig(s, true); const run = s.run(); @@ -297,6 +297,12 @@ async function writeSwcrcConfig(s, config) { s.write(".swcrc", JSON.stringify(json, null, 2) + "\n"); } +async function writeSwcConfigJs(s, config) { + // Create a JavaScript file that exports the configuration + const jsContent = `module.exports = ${JSON.stringify(config, null, 2)};`; + s.write("swc.config.js", jsContent); +} + selftest.define("modern build stack - transpiler custom .swcrc", async function () { const currentMeteorModern = process.env.METEOR_MODERN; process.env.METEOR_MODERN = ''; @@ -328,6 +334,50 @@ selftest.define("modern build stack - transpiler custom .swcrc", async function process.env.METEOR_MODERN = currentMeteorModern; }); +selftest.define("modern build stack - transpiler custom swc.config.js", async function () { + const currentMeteorModern = process.env.METEOR_MODERN; + process.env.METEOR_MODERN = ''; + + const s = new Sandbox(); + await s.init(); + + await s.createApp("modern", "modern"); + await s.cd("modern"); + + // Remove the .swcrc file to ensure we're using swc.config.js + s.unlink(".swcrc"); + + // Write the swc.config.js file with the same configuration + await writeSwcConfigJs(s, { + jsc: { + baseUrl: "./", + paths: { + "@swcAlias/*": ["swcAlias/*"] + } + } + }); + + await writeConfig(s, { + modern: true, + mainModule: { + client: 'client/main.js', + server: 'server/alias.js', + }, + }); + + const run = s.run(); + + run.waitSecs(waitToStart); + await run.match("App running at"); + + /* custom swc.config.js and alias resolution */ + await run.match(/alias resolved/, false, true); + + await run.stop(); + + process.env.METEOR_MODERN = currentMeteorModern; +}); + selftest.define("modern build stack - transpiler files", async function () { process.env.METEOR_MODERN = 'true'; const s = new Sandbox(); @@ -442,7 +492,7 @@ selftest.define("modern build stack - test swc minifier", async function () { await s.createApp(appName, "modern"); await s.cd(appName); - + await writeConfig(s, { modern: true, mainModule: { @@ -452,7 +502,7 @@ selftest.define("modern build stack - test swc minifier", async function () { }); s.set("NODE_INSPECTOR_IPC", "1"); - + await writeModernConfig(s, { minifier: true });