From c46681f4dce2a2d8b513705fa83764af8a240ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Fri, 18 Jul 2025 14:13:48 +0200 Subject: [PATCH] add Coffeescript dependency check and installation logic --- npm-packages/meteor-rspack/rspack.config.js | 43 ++++++++-- packages/rspack/lib/constants.js | 20 ++--- packages/rspack/lib/dependencies.js | 88 ++++++++++++++++++++- packages/rspack/lib/processes.js | 1 + packages/rspack/rspack_plugin.js | 6 +- packages/tools-core/lib/meteor.js | 8 ++ 6 files changed, 144 insertions(+), 22 deletions(-) diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index 6bbc916885..2317b210d8 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -71,6 +71,22 @@ function createSwcConfig({ isRun, isTypescriptEnabled, isJsxEnabled, isTsxEnable }; } +// Coffeescript rule +function createCoffeescriptConfig({ swcConfig }) { + return { + test: /\.coffee$/, + use: [ + { + loader: 'swc-loader', + options: swcConfig, + }, + { + loader: 'coffee-loader', + }, + ], + }; +} + // Watch options shared across both builds const watchOptions = { ignored: ['**/.meteor/local/**', '**/dist/**'], @@ -102,8 +118,12 @@ export default function (inMeteor = {}, argv = {}) { const mode = isProd ? 'production' : 'development'; const isTypescriptEnabled = Meteor.isTypescriptEnabled || false; - const isJsxEnabled = Meteor.isJsxEnabled || false; - const isTsxEnabled = Meteor.isTsxEnabled || false; + const isJsxEnabled = + Meteor.isJsxEnabled || (!isTypescriptEnabled && isReactEnabled) || false; + const isTsxEnabled = + Meteor.isTsxEnabled || (isTypescriptEnabled && isReactEnabled) || false; + + const isCoffeescriptEnabled = Meteor.isCoffeescriptEnabled || false; // Determine entry points const entryPath = Meteor.entryPath; @@ -132,7 +152,7 @@ export default function (inMeteor = {}, argv = {}) { console.log('[i] Meteor flags:', Meteor); } - const swcConfig = createSwcConfig({ + const swcConfigRule = createSwcConfig({ isRun, isTypescriptEnabled, isJsxEnabled, @@ -142,7 +162,11 @@ export default function (inMeteor = {}, argv = {}) { /^meteor.*/, ...(isReactEnabled ? [/^react$/, /^react-dom$/] : []) ]; + const alias = { + '/': path.resolve(process.cwd()), + }; const extensions = [ + ...(isCoffeescriptEnabled ? ['.coffee'] : []), '.ts', '.tsx', '.js', @@ -152,9 +176,11 @@ export default function (inMeteor = {}, argv = {}) { '.json', '.wasm', ]; - const alias = { - '/': path.resolve(process.cwd()), - }; + const extraRules = [ + ...(isCoffeescriptEnabled + ? [createCoffeescriptConfig({ swcConfig: swcConfigRule?.options })] + : []), + ]; // Base client config let clientConfig = { @@ -177,7 +203,7 @@ export default function (inMeteor = {}, argv = {}) { }, module: { rules: [ - swcConfig, + swcConfigRule, ...(Meteor.isBlazeEnabled ? [ { @@ -186,6 +212,7 @@ export default function (inMeteor = {}, argv = {}) { }, ] : []), + ...extraRules, ], }, resolve: { extensions, alias }, @@ -256,7 +283,7 @@ export default function (inMeteor = {}, argv = {}) { }, optimization: { usedExports: true }, module: { - rules: [swcConfig], + rules: [swcConfigRule, ...extraRules], }, resolve: { extensions, diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index 80ec3cd614..cf776ef103 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -3,24 +3,18 @@ * @description Constants and global state keys for RSPack plugin */ -/** - * Default RSPack version to install if not found - * @constant {string} - */ export const DEFAULT_RSPACK_VERSION = '1.4.8'; -/** - * Default Meteor RSPack version to install if not found - * @constant {string} - */ export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.4'; -/** - * Default RSPack React HMRversion to install if not found - * @constant {string} - */ export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; +export const DEFAULT_METEOR_RSPACK_COFFEESCRIPT_VERSION = '2.7.0'; + +export const DEFAULT_METEOR_RSPACK_COFFEE_LOADER_VERSION = '5.0.0'; + +export const DEFAULT_METEOR_RSPACK_SWC_LOADER_VERSION = '0.2.6'; + /** * Global state keys used for storing and retrieving state across the application * @constant {Object} @@ -37,6 +31,8 @@ export const GLOBAL_STATE_KEYS = { SERVER_PROCESS: 'rspack.serverProcess', RSPACK_INSTALLATION_CHECKED: 'rspack.rspackInstallationChecked', RSPACK_REACT_INSTALLATION_CHECKED: 'rspack.rspackReactInstallationChecked', + COFFEESCRIPT_CHECKED: 'rspack.coffeescriptChecked', + RSPACK_COFFEESCRIPT_INSTALLATION_CHECKED: 'rspack.rspackCoffeescriptInstallationChecked', REACT_CHECKED: 'rspack.reactChecked', INITIAL_ENTRYPONTS: 'meteor.initialEntrypoints', CLIENT_FIRST_COMPILE: 'rspack.clientFirstCompile', diff --git a/packages/rspack/lib/dependencies.js b/packages/rspack/lib/dependencies.js index 880d0a1278..35bcd02e8f 100644 --- a/packages/rspack/lib/dependencies.js +++ b/packages/rspack/lib/dependencies.js @@ -11,7 +11,10 @@ const { logProgress, logSuccess, } = require('meteor/tools-core/lib/log'); -const { getMeteorAppDir } = require('meteor/tools-core/lib/meteor'); +const { + getMeteorAppDir, + isMeteorCoffeescriptProject, +} = require('meteor/tools-core/lib/meteor'); const { checkNpmDependencyExists, installNpmDependency, @@ -154,3 +157,86 @@ export async function ensureRSPackReactInstalled() { // Mark as checked setGlobalState(GLOBAL_STATE_KEYS.RSPACK_REACT_INSTALLATION_CHECKED, true); } + +/** + * Checks if Coffeescript is installed and sets global state accordingly + * Sets global state and environment variables based on Coffeescript detection + * @returns {Promise} A promise that resolves when the check is complete + */ +export async function checkCoffeescriptInstalled() { + // Skip if already checked + if (getGlobalState(GLOBAL_STATE_KEYS.COFFEESCRIPT_CHECKED, false)) { + return; + } + + const appDir = getMeteorAppDir(); + const isCoffescriptInstalled = + checkNpmDependencyExists('coffeescript', { cwd: appDir }) || + isMeteorCoffeescriptProject(); + + if (isCoffescriptInstalled) { + // Set environment variable to indicate React is enabled + process.env.METEOR_COFFEESCRIPT_ENABLED = 'true'; + } else { + process.env.METEOR_COFFEESCRIPT_ENABLED = 'false'; + } + + // Mark as checked + setGlobalState(GLOBAL_STATE_KEYS.COFFEESCRIPT_CHECKED, true); + + return isCoffescriptInstalled; +} + +export async function ensureRSPackCoffeescriptInstalled() { + // Skip if already checked + if (getGlobalState(GLOBAL_STATE_KEYS.RSPACK_COFFEESCRIPT_INSTALLATION_CHECKED, false)) { + return; + } + + const appDir = getMeteorAppDir(); + const isRSPackCoffeescriptInstalled = + checkNpmDependencyExists('coffeescript', { cwd: appDir }) && + checkNpmDependencyVersion('coffeescript', { + cwd: appDir, + versionRequirement: DEFAULT_METEOR_RSPACK_COFFEESCRIPT_VERSION, + semverCondition: 'gte', + }) && + checkNpmDependencyExists('coffee-loader', { cwd: appDir }) && + checkNpmDependencyVersion('coffee-loader', { + cwd: appDir, + versionRequirement: DEFAULT_METEOR_RSPACK_COFFEE_LOADER_VERSION, + semverCondition: 'gte', + }) && + checkNpmDependencyExists('swc-loader', { cwd: appDir }) && + checkNpmDependencyVersion('swc-loader', { + cwd: appDir, + versionRequirement: DEFAULT_METEOR_RSPACK_SWC_LOADER_VERSION, + semverCondition: 'gte', + }); + + const rspackCoffeescriptDependencies = [ + `coffeescript@${DEFAULT_METEOR_RSPACK_COFFEESCRIPT_VERSION}`, + `coffee-loader@${DEFAULT_METEOR_RSPACK_COFFEE_LOADER_VERSION}`, + `swc-loader@${DEFAULT_METEOR_RSPACK_SWC_LOADER_VERSION}`, + ]; + if (!isRSPackCoffeescriptInstalled) { + logProgress( + `RSPack Coffeescript not found. Installing ${joinWithAnd(rspackCoffeescriptDependencies)}...`, + ); + const success = await installNpmDependency(rspackCoffeescriptDependencies, { + cwd: appDir, + dev: true, + }); + + if (!success) { + throw new Error( + `Failed to install RSPack Coffeescript. Please install it manually with: meteor npm install -D ${joinWithAnd(rspackCoffeescriptDependencies)}` + ); + } + + logSuccess('RSPack Coffeescript installed successfully.'); + } + + // Mark as checked + setGlobalState(GLOBAL_STATE_KEYS.RSPACK_COFFEESCRIPT_INSTALLATION_CHECKED, true); +} diff --git a/packages/rspack/lib/processes.js b/packages/rspack/lib/processes.js index d3e9ab0e1a..94c7756f34 100644 --- a/packages/rspack/lib/processes.js +++ b/packages/rspack/lib/processes.js @@ -112,6 +112,7 @@ export function getRSPackEnv({ isClient, isServer }) { ['isTypescriptEnabled', isTypescriptEnabled], ['isTsxEnabled', isTsxEnabled], ['isJsxEnabled', isJsxEnabled], + ['isCoffeescriptEnabled', process.env.METEOR_COFFEESCRIPT_ENABLED], ]; return pairs.flatMap(([key, val]) => [ '--env', diff --git a/packages/rspack/rspack_plugin.js b/packages/rspack/rspack_plugin.js index e7ad7a98f8..1e70c41e93 100644 --- a/packages/rspack/rspack_plugin.js +++ b/packages/rspack/rspack_plugin.js @@ -21,7 +21,7 @@ const { const { ensureRSPackInstalled, - checkReactInstalled, ensureRSPackReactInstalled, + checkReactInstalled, ensureRSPackReactInstalled, checkCoffeescriptInstalled, } = require('./lib/dependencies'); const { @@ -72,6 +72,10 @@ try { await ensureRSPackReactInstalled(); } + if (checkCoffeescriptInstalled()) { + await checkCoffeescriptInstalled(); + } + // Ensure the RSPack build context directory exists ensureRSPackBuildContextExists(); diff --git a/packages/tools-core/lib/meteor.js b/packages/tools-core/lib/meteor.js index 4984ebaf62..5c17b67c9d 100644 --- a/packages/tools-core/lib/meteor.js +++ b/packages/tools-core/lib/meteor.js @@ -285,3 +285,11 @@ export function isMeteorBlazeProject() { export function isMeteorBlazeHotProject() { return getMeteorAppPackages().includes('blaze-hot'); } + +/** + * Checks if the Meteor application is a Coffeescript project. + * @returns {boolean} + */ +export function isMeteorCoffeescriptProject() { + return getMeteorAppPackages().includes('coffeescript'); +}