Merge branch 'modern-bundler-integration' into release-3.4

This commit is contained in:
Nacho Codoñer
2025-09-11 14:35:23 +02:00
8 changed files with 133 additions and 15 deletions

View File

@@ -4,6 +4,7 @@
*/
import { glob } from 'glob';
import path from 'path';
import fs from 'fs';
const { logInfo } = require('meteor/tools-core/lib/log');
const {
@@ -22,6 +23,7 @@ const {
isMeteorScssProject,
getMeteorEnvPackageDirs,
getMeteorAppConfig,
getMeteorAppDir,
} = require('meteor/tools-core/lib/meteor');
const { buildUnignorePatterns } = require('meteor/tools-core/lib/ignore');
@@ -30,6 +32,53 @@ import { getInitialEntrypoints } from './build-context';
const { ensureModuleFilesExist, getBuildFilePath } = require('./build-context');
const { RSPACK_BUILD_CONTEXT, FILE_ROLE } = require('./constants');
/**
* Checks if exact entries exist in the .meteorignore file
* @param {string[]} entries - Array of exact entries to check for
* @returns {Object} Object with keys corresponding to each entry and values as booleans
*/
function checkMeteorIgnoreExactEntries(entries) {
const meteorIgnorePath = path.join(getMeteorAppDir(), '.meteorignore');
const results = {};
// Initialize results object with false for each entry
entries.forEach(entry => {
results[entry] = false;
});
// Check if .meteorignore file exists
if (!fs.existsSync(meteorIgnorePath)) {
return results;
}
// Read the .meteorignore file
try {
const content = fs.readFileSync(meteorIgnorePath, 'utf8');
const lines = content.split('\n');
// Check each line against all entries
lines.forEach(line => {
// Skip empty lines and comments
if (!line.trim() || line.trim().startsWith('#')) {
return;
}
const trimmedLine = line.trim();
// Check for exact matches
entries.forEach(entry => {
if (trimmedLine === entry) {
results[entry] = true;
}
});
});
} catch (error) {
// If there's an error reading the file, return the initialized results
}
return results;
}
/**
* Gets the list of file extensions to ignore based on project type
* For Blaze projects, it excludes .html as used by Blaze
@@ -144,10 +193,31 @@ export function configureMeteorForRspack() {
// Skip immediate html and css children from intial entrypoint contexts
extraFilesToIgnore = [
...extraFilesToIgnore,
...initialEntrypointContexts.flatMap(entrypoint => [
`!${entrypoint}/*.html`,
`!${entrypoint}/*.css`,
]),
...initialEntrypointContexts.flatMap(entrypoint => {
// Create exact entries to check for
const cssEntry = `${entrypoint}/*.css`;
const htmlEntry = `${entrypoint}/*.html`;
// Check all entries at once
const entryResults = checkMeteorIgnoreExactEntries([cssEntry, htmlEntry]);
const hasMatchingCssEntry = entryResults[cssEntry];
const hasMatchingHtmlEntry = entryResults[htmlEntry];
// Prepare the result array
const result = [];
// Only add the HTML ignore pattern if there's no matching HTML entry in .meteorignore
if (!hasMatchingHtmlEntry) {
result.push(`!${htmlEntry}`);
}
// Only add the CSS ignore pattern if there's no matching CSS entry in .meteorignore
if (!hasMatchingCssEntry) {
result.push(`!${cssEntry}`);
}
return result;
}),
];
const testIgnorePath = `${RSPACK_BUILD_CONTEXT}/${path.dirname(

View File

@@ -215,18 +215,40 @@ export async function assertConsoleEval(code, expectedResult, options = {}) {
}
/**
* Helper function to assert that the body element has the expected CSS styles
* Helper function to assert that an element has the expected CSS styles
* @param {string} selector - CSS selector string (e.g., 'body', '.my-class') or a string representing a DOM element (e.g., 'document.body')
* @param {Object} expectedStyles - Expected CSS styles as key-value pairs
* @param {Object} options - Additional options for assertConsoleEval
* @returns {Promise<Object>} - A promise that resolves with the computed styles
*/
export async function assertBodyStyles(expectedStyles, options = {}) {
console.log(`Asserting body styles: ${JSON.stringify(expectedStyles)}`);
export async function assertStyles(selector, expectedStyles, options = {}) {
// Determine if the selector is a CSS selector or a DOM element reference
const isCssSelector = selector.startsWith('.') ||
selector.startsWith('#') ||
selector.startsWith('[') ||
selector === 'body' ||
selector.includes(' ');
console.log(`Asserting styles for ${selector}: ${JSON.stringify(expectedStyles)}`);
// Create a JavaScript code string that evaluates the computed styles
const code = `
(() => {
const computedStyle = getComputedStyle(document.body);
let element;
// Handle the selector based on its type
${isCssSelector
? `element = document.querySelector('${selector}');
if (!element) {
throw new Error('Element not found with selector: ${selector}');
}`
: `element = ${selector};
if (!element) {
throw new Error('Element not found: ${selector}');
}`
}
const computedStyle = getComputedStyle(element);
const result = {};
${Object.keys(expectedStyles).map(prop =>
`result['${prop}'] = computedStyle.getPropertyValue('${prop}');`
@@ -236,12 +258,23 @@ export async function assertBodyStyles(expectedStyles, options = {}) {
`;
// Use assertConsoleEval to evaluate the code and check the result
return await assertConsoleEval(code, expectedStyles, {
return assertConsoleEval(code, expectedStyles, {
exactMatch: false,
...options
});
}
/**
* Helper function to assert that the body element has the expected CSS styles
* @param {Object} expectedStyles - Expected CSS styles as key-value pairs
* @param {Object} options - Additional options for assertConsoleEval
* @returns {Promise<Object>} - A promise that resolves with the computed styles
*/
export async function assertBodyStyles(expectedStyles, options = {}) {
// Use assertStyles with document.body as the selector
return assertStyles('document.body', expectedStyles, options);
}
/**
* Helper function to assert that meta tags have the expected content
* @param {Object} expectedMetaTags - Expected meta tag properties and values as key-value pairs

View File

@@ -6,6 +6,7 @@
import { testMeteorSkeleton } from "./test-helpers";
import { assertStyles } from "./assertions";
describe('Meteor Skeletons /', () => {
describe('Apollo Skeleton /', testMeteorSkeleton({
@@ -87,6 +88,20 @@ describe('Meteor Skeletons /', () => {
server: 'server/main.ts',
test: 'tests/main.ts',
},
customAssertions: {
afterRun: async ({ port }) => {
// Verify Tailwind styles for ".bg-gray-100" element
await assertStyles('.bg-gray-100', {
['background-color']: 'oklch(0.967 0.003 264.542)',
});
},
afterRunProduction: async ({ port }) => {
// Verify Tailwind styles for ".bg-gray-100" element
await assertStyles('.bg-gray-100', {
['background-color']: 'lab(96.1596 -0.0823438 -1.13575)',
});
}
}
}));
describe('Typescript Skeleton /', testMeteorSkeleton({

View File

@@ -0,0 +1,2 @@
# Ignore Meteor CSS handling; let Rspack resolve Tailwind styles
client/*.css

View File

@@ -1,6 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
body {
padding: 10px;

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { App } from '/imports/ui/App';
import './main.css';
Meteor.startup(() => {
render(<App/>, document.getElementById('react-target'));

View File

@@ -4,7 +4,6 @@ import { Info } from './Info.jsx';
export const App = () => (
<div className="max-w-3xl min-h-screen mx-auto sm:pt-10">
<h1>Welcome to Meteor!</h1>
<Hello/>
<Info/>
</div>

View File

@@ -13,9 +13,9 @@ export const Hello = () => {
<div className="sm:flex sm:items-start sm:justify-between">
<div>
<div className="flex items-center">
<h3 className="text-3xl text-gray-900 font-bold">
<h1 className="text-3xl text-gray-900 font-bold">
Welcome to Meteor!
</h3>
</h1>
<div>
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 400 400"><g fill="#DE4F4F"><path d="M286.575 306.886L44.755 49.922l256.962 241.82c4.312 4.056 4.518 10.837.46 15.146-4.053 4.31-10.832 4.518-15.144.46-.15-.14-.318-.31-.458-.462M251.032 325.01L68.692 127.528 266.177 309.87c4.35 4.013 4.618 10.794.604 15.144-4.018 4.35-10.794 4.617-15.146.604-.2-.19-.413-.406-.602-.607M214.083 325.542L92.907 194.272 224.18 315.446c2.898 2.676 3.077 7.197.402 10.098-2.677 2.896-7.195 3.082-10.097.402-.136-.125-.277-.272-.402-.405M315.612 234.685L189.102 98.078 325.71 224.585c2.896 2.684 3.067 7.203.387 10.1-2.682 2.895-7.2 3.066-10.098.387-.13-.123-.268-.258-.388-.387M304.697 272.93L121.567 74.655l198.274 183.13c4.35 4.017 4.62 10.796.605 15.144-4.017 4.352-10.797 4.617-15.146.604-.205-.19-.418-.404-.603-.605M176.31 314.783l-57.647-62.695 62.692 57.65c1.453 1.334 1.547 3.596.215 5.045-1.338 1.453-3.598 1.55-5.05.215-.072-.07-.144-.143-.21-.215M311.093 189.297l-57.65-62.694 62.696 57.646c1.45 1.335 1.546 3.597.21 5.048-1.335 1.45-3.595 1.547-5.05.21-.07-.065-.143-.143-.207-.21"/></g></svg>
</div>