diff --git a/npm-packages/meteor-rspack/lib/ignore.js b/npm-packages/meteor-rspack/lib/ignore.js index 6123d97deb..17058cce19 100644 --- a/npm-packages/meteor-rspack/lib/ignore.js +++ b/npm-packages/meteor-rspack/lib/ignore.js @@ -1,172 +1,139 @@ var fs = require('fs'); var path = require('path'); -// Cleans an entry from wildcard patterns (*/**) -function cleanWildcardEntry(entry) { - // If it's an extension pattern like *.ext, skip it - if (entry.match(/\*\.[^\/]+$/)) { - return null; - } - - // Handle patterns like my-folder/**/* by extracting the folder part - if (entry.includes('/**/')) { - const folderContext = entry.split('/**/')[0].replace(/\/+$/, ''); - if (folderContext) { - return folderContext; - } - } - - // Otherwise, extract the folder context by removing the wildcard part - if (entry.includes('*')) { - const folderContext = entry.split('*')[0].replace(/\/+$/, ''); - if (folderContext) { - return folderContext; - } - return null; - } - - return entry; -} - /** * Reads the .meteorignore file from the given project directory and returns - * the parsed entries. + * the parsed entries. Empty lines and comment lines (starting with #) are filtered out. * * @param {string} projectDir - The project directory path - * @returns {Object} - Object with rootFolders and nestedFolders arrays + * @returns {string[]} - Array of ignore patterns */ const getMeteorIgnoreEntries = function (projectDir) { const meteorIgnorePath = path.join(projectDir, '.meteorignore'); // Check if .meteorignore file exists - let entries = []; try { const fileContent = fs.readFileSync(meteorIgnorePath, 'utf8'); // Process each line in the file - entries = fileContent.split(/\r?\n/).filter(line => { - // Trim the line - const trimmedLine = line.trim(); - // Skip empty lines, comments, and negation entries (starting with !) - return trimmedLine !== '' && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('!'); - }).map(line => line.trim()); // Ensure all lines are trimmed + const entries = fileContent.split(/\r?\n/) + .map(line => line.trim()) + .filter(line => line !== '' && !line.startsWith('#')); - // Clean all entries from wildcard patterns (*/** parts) - entries = entries.map(entry => { - return cleanWildcardEntry(entry); - }).filter(entry => entry !== null); - - // Separate entries into rootFolders and nestedFolders - const rootFolders = []; - const nestedFolders = []; - - entries.forEach(entry => { - // If entry starts with / or ./, it's a root folder - if (entry.startsWith('/') || entry.startsWith('./')) { - rootFolders.push(entry); - } else { - // Otherwise, it's a nested folder - nestedFolders.push(entry); - } - }); - - return { rootFolders, nestedFolders }; + return entries; } catch (e) { - // If the file doesn't exist or can't be read, return empty arrays - return { rootFolders: [], nestedFolders: [] }; + // If the file doesn't exist or can't be read, return empty array + return []; } }; /** - * Creates a regex pattern to ignore specified folders. - * The pattern will match paths where the specified folders appear as complete path segments. - * Special regex characters in folder names are automatically escaped. - * @param {Object|string[]} options - Options object - * @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path - * @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level - * @returns {RegExp} - Regex pattern to ignore the specified folders + * Creates a glob config array for ignoring specified patterns. + * Transforms .gitignore-style entries into chokidar-compatible glob patterns. + * @param {string[]} entries - Array of .gitignore-style patterns + * @returns {string[]} - Array of glob patterns for chokidar */ -function createIgnoreFoldersRegex(options) { - const nestedFolders = options.nestedFolders || []; - const rootFolders = options.rootFolders || []; - - if (!Array.isArray(nestedFolders) || nestedFolders.length === 0) { - throw new Error('nestedFolders must be a non-empty array'); +function createIgnoreGlobConfig(entries = []) { + if (!Array.isArray(entries)) { + throw new Error('Entries must be an array'); } - // If rootFolders is not provided or empty, use the original behavior - if (!rootFolders || !Array.isArray(rootFolders) || rootFolders.length === 0) { - // Escape special regex characters in folder names - const escapedFolders = nestedFolders.map(folder => - folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - ); - - // Join folder names with | for the regex pattern - const foldersPattern = escapedFolders.join('|'); - - // Create a regex that matches paths where the specified folders appear as complete path segments - // Format: /(^|\/)(folder1|folder2|folder3)(\/|$)/ - return new RegExp(`(^|\\/)(${foldersPattern})(\\/|$)`); - } - - // Handle both rootFolders and nestedFolders - // Escape special regex characters in folder names - const escapedNestedFolders = nestedFolders.map(folder => - folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - ); - - const escapedRootFolders = rootFolders.map(folder => - folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - ); - - // Join folder names with | for the regex patterns - const nestedFoldersPattern = escapedNestedFolders.join('|'); - const rootFoldersPattern = escapedRootFolders.join('|'); - - // Create a regex that matches: - // 1. Root folders at the beginning of the path: /^(folderRootOnly)(\/|$)/ - // 2. Nested folders anywhere in the path: /(^|\/)(folderAny1|folderAny2)(\/|$)/ - const pattern = `^(${rootFoldersPattern})(\\/|$)|(^|\\/)(${nestedFoldersPattern})(\\/|$)`; - return new RegExp(pattern); -} - -/** - * Creates a glob config array for ignoring specified folders. - * For nested folders, the pattern will be "**/" + folder + "/**". - * For root folders, the pattern will be folder + "/**". - * @param {Object} options - Options object - * @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path - * @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level - * @returns {string[]} - Array of glob patterns to ignore the specified folders - */ -function createIgnoreGlobConfig(options = {}) { - const nestedFolders = options.nestedFolders || []; - const rootFolders = options.rootFolders || []; const globPatterns = []; - // Create glob patterns for nested folders: **/{nestedFolder}/** - if (Array.isArray(nestedFolders) && nestedFolders.length > 0) { - nestedFolders.forEach(folder => { - // Remove leading ./ or / if present - const cleanFolder = folder.replace(/^(\.\/|\/)/g, ''); - globPatterns.push(`**/${cleanFolder}/**`); - }); - } + entries.forEach(entry => { + // Skip empty entries + if (!entry.trim()) { + return; + } - // Create glob patterns for root folders: {rootFolder}/** - if (Array.isArray(rootFolders) && rootFolders.length > 0) { - rootFolders.forEach(folder => { - // Remove leading ./ or / if present - const cleanFolder = folder.replace(/^(\.\/|\/)/g, ''); - globPatterns.push(`${cleanFolder}/**`); - }); - } + // Handle comments + if (entry.startsWith('#')) { + return; + } + + // Check if it's a negation pattern + const isNegation = entry.startsWith('!'); + let pattern = isNegation ? entry.substring(1).trim() : entry.trim(); + + // Remove leading ./ or / if present + pattern = pattern.replace(/^(\.\/|\/)/g, ''); + + // If it ends with /, it's a directory pattern, add ** to match all contents + if (pattern.endsWith('/')) { + pattern = pattern.slice(0, -1) + '/**'; + } + + // If it doesn't include a /, it could match anywhere in the path + if (!pattern.includes('/')) { + pattern = '**/' + pattern; + } else if (!pattern.startsWith('**/') && !pattern.startsWith('/')) { + // If it has a / but doesn't start with **/, add **/ to match anywhere + pattern = '**/' + pattern; + } + + // Add the negation back if it was present + if (isNegation) { + pattern = '!' + pattern; + } + + globPatterns.push(pattern); + }); return globPatterns; } +/** + * Creates a regex pattern to match the specified glob patterns. + * Converts glob patterns with * and ** into regex equivalents. + * + * @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig + * @returns {RegExp} - Regex pattern to match the specified patterns + */ +function createIgnoreRegex(globPatterns) { + if (!Array.isArray(globPatterns) || globPatterns.length === 0) { + throw new Error('globPatterns must be a non-empty array'); + } + + // Process each glob pattern and convert to regex + const regexPatterns = globPatterns.map(pattern => { + // Skip negation patterns for the regex + if (pattern.startsWith('!')) { + return null; + } + + // Escape special regex characters, but not * and / + let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + + // Use a temporary placeholder for ** that won't be affected by the * replacement + // This is necessary because if we directly replace ** with .* and then replace * with [^/]* + const DOUBLE_ASTERISK_PLACEHOLDER = '__DOUBLE_ASTERISK__'; + regexPattern = regexPattern.replace(/\*\*/g, DOUBLE_ASTERISK_PLACEHOLDER); + + // Convert * to regex equivalent (any number of characters except /) + regexPattern = regexPattern.replace(/\*/g, '[^/]*'); + + // Convert the ** placeholder to its regex equivalent (any number of characters including /) + regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*'); + + // For absolute paths, we don't want to force the pattern to match from the beginning + // but we still want to ensure it matches to the end of the path segment + regexPattern = '(?:^|/)' + regexPattern + '$'; + + return regexPattern; + }).filter(pattern => pattern !== null); + + if (regexPatterns.length === 0) { + // If all patterns were negations, return a regex that matches nothing + return new RegExp('^$'); + } + + // Join all patterns with | to create a single regex + const combinedPattern = regexPatterns.join('|'); + return new RegExp(combinedPattern); +} + module.exports = { - createIgnoreFoldersRegex, + createIgnoreRegex, getMeteorIgnoreEntries, createIgnoreGlobConfig, }; diff --git a/npm-packages/meteor-rspack/lib/test.js b/npm-packages/meteor-rspack/lib/test.js index cd4adf3122..6301c73681 100644 --- a/npm-packages/meteor-rspack/lib/test.js +++ b/npm-packages/meteor-rspack/lib/test.js @@ -1,6 +1,6 @@ const fs = require('fs'); const path = require('path'); -const { createIgnoreFoldersRegex } = require("./ignore.js"); +const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js"); /** * Generates eager test files dynamically @@ -8,33 +8,34 @@ const { createIgnoreFoldersRegex } = require("./ignore.js"); * @param {boolean} options.isAppTest - Whether this is an app test * @param {string} options.projectDir - The project directory * @param {string} options.buildContext - The build context - * @param {string} options.rootFolders - * @param {string} options.nestedFolders + * @param {string[]} options.entries - Array of ignore patterns * @returns {string} The path to the generated file */ const generateEagerTestFile = ({ isAppTest, projectDir, buildContext, - rootFolders, - nestedFolders, + entries = [], }) => { const distDir = path.resolve(projectDir, ".meteor/local/test"); if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir, { recursive: true }); } - const excludeFoldersRegex = createIgnoreFoldersRegex({ - nestedFolders: [ - "node_modules", - ".meteor", - "public", - "private", - buildContext, - ...nestedFolders, - ], - rootFolders, - }); + // Combine all ignore entries + const ignoreEntries = [ + "**/node_modules/**", + "**/.meteor/**", + "**/public/**", + "**/private/**", + `**/${buildContext}/**`, + ...entries, + ]; + + // Create regex from ignore entries + const excludeFoldersRegex = createIgnoreRegex( + createIgnoreGlobConfig(ignoreEntries) + ); const filename = isAppTest ? "eager-app-tests.mjs" : "eager-tests.mjs"; const filePath = path.resolve(distDir, filename); diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json index dfa6e14388..121e31d2e4 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.53", + "version": "0.0.54", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@meteorjs/rspack", - "version": "0.0.53", + "version": "0.0.54", "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 a2502e779f..f4745c6dcf 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.53", + "version": "0.0.54", "description": "Configuration logic for using Rspack in Meteor projects", "main": "index.js", "type": "commonjs", diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index 9b415a988b..13701e3e13 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -195,22 +195,24 @@ module.exports = async function (inMeteor = {}, argv = {}) { }; // Get Meteor ignore entries - const { rootFolders, nestedFolders } = getMeteorIgnoreEntries(projectDir); + const meteorIgnoreEntries = getMeteorIgnoreEntries(projectDir); + + // Additional ignore entries + const additionalEntries = [ + "**/.meteor/local/**", + "**/dist/**", + ...(isTest && isTestEager + ? [`**/${buildContext}/**`, "**/.meteor/local/**", "node_modules/**"] + : []), + ]; // Set default watch options const watchOptions = { ignored: [ - ...createIgnoreGlobConfig({ - rootFolders, - nestedFolders: [ - ".meteor/local", - "dist", - ...(isTest && isTestEager - ? [buildContext, ".meteor/local", "node_modules"] - : []), - ...(nestedFolders || []), - ], - }), + ...createIgnoreGlobConfig([ + ...meteorIgnoreEntries, + ...additionalEntries, + ]), ], }; @@ -379,16 +381,14 @@ module.exports = async function (inMeteor = {}, argv = {}) { isAppTest: true, projectDir, buildContext, - rootFolders, - nestedFolders, + entries: meteorIgnoreEntries, }) : isTest && isTestEager ? generateEagerTestFile({ isAppTest: false, projectDir, buildContext, - rootFolders, - nestedFolders, + entries: meteorIgnoreEntries, }) : path.resolve(projectDir, buildContext, entryPath); const serverNameConfig = `[${(isTest && 'test-') || ''}${ diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index c94bcce28f..257fa9ae63 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -5,7 +5,7 @@ export const DEFAULT_RSPACK_VERSION = '1.5.3'; -export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.53'; +export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.54'; 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 b826964dd0..092833dc8a 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.53", + "@meteorjs/rspack": "^0.0.54", "@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 7c61d053ce..c21de5ce5f 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.53", + "@meteorjs/rspack": "^0.0.54", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", "@rspack/plugin-react-refresh": "^1.4.3", diff --git a/tools/static-assets/skel-babel/package.json b/tools/static-assets/skel-babel/package.json index 2152d9f321..042f9b3eab 100644 --- a/tools/static-assets/skel-babel/package.json +++ b/tools/static-assets/skel-babel/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@babel/preset-env": "^7.28.3", "@babel/preset-react": "^7.23.3", - "@meteorjs/rspack": "^0.0.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index f1b3f08225..0a75f00bf4 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index 7ffd7966b2..d84dde8416 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-coffee/package.json b/tools/static-assets/skel-coffee/package.json index a264d8020a..0abbee8276 100644 --- a/tools/static-assets/skel-coffee/package.json +++ b/tools/static-assets/skel-coffee/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index bcd750452d..dbfa49f93f 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index 202dda9cc4..20bb8d1470 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index 3e47c0ef54..621cd9f703 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index 950f1da277..af7aeaf929 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index d6fa7323a7..da9e5c7bdf 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index e877c05d0b..1a4a81cde3 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3", diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json index 7dee7a0225..2882f7751e 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.53", + "@meteorjs/rspack": "^0.0.54", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.3", "@rspack/core": "^1.5.3",