@meteorjs/rspack 0.0.50 → 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ignore.js ADDED
@@ -0,0 +1,172 @@
1
+ var fs = require('fs');
2
+ var path = require('path');
3
+
4
+ // Cleans an entry from wildcard patterns (*/**)
5
+ function cleanWildcardEntry(entry) {
6
+ // If it's an extension pattern like *.ext, skip it
7
+ if (entry.match(/\*\.[^\/]+$/)) {
8
+ return null;
9
+ }
10
+
11
+ // Handle patterns like my-folder/**/* by extracting the folder part
12
+ if (entry.includes('/**/')) {
13
+ const folderContext = entry.split('/**/')[0].replace(/\/+$/, '');
14
+ if (folderContext) {
15
+ return folderContext;
16
+ }
17
+ }
18
+
19
+ // Otherwise, extract the folder context by removing the wildcard part
20
+ if (entry.includes('*')) {
21
+ const folderContext = entry.split('*')[0].replace(/\/+$/, '');
22
+ if (folderContext) {
23
+ return folderContext;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ return entry;
29
+ }
30
+
31
+ /**
32
+ * Reads the .meteorignore file from the given project directory and returns
33
+ * the parsed entries.
34
+ *
35
+ * @param {string} projectDir - The project directory path
36
+ * @returns {Object} - Object with rootFolders and nestedFolders arrays
37
+ */
38
+ const getMeteorIgnoreEntries = function (projectDir) {
39
+ const meteorIgnorePath = path.join(projectDir, '.meteorignore');
40
+
41
+ // Check if .meteorignore file exists
42
+ let entries = [];
43
+ try {
44
+ const fileContent = fs.readFileSync(meteorIgnorePath, 'utf8');
45
+
46
+ // Process each line in the file
47
+ entries = fileContent.split(/\r?\n/).filter(line => {
48
+ // Trim the line
49
+ const trimmedLine = line.trim();
50
+ // Skip empty lines, comments, and negation entries (starting with !)
51
+ return trimmedLine !== '' && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('!');
52
+ }).map(line => line.trim()); // Ensure all lines are trimmed
53
+
54
+ // Clean all entries from wildcard patterns (*/** parts)
55
+ entries = entries.map(entry => {
56
+ return cleanWildcardEntry(entry);
57
+ }).filter(entry => entry !== null);
58
+
59
+ // Separate entries into rootFolders and nestedFolders
60
+ const rootFolders = [];
61
+ const nestedFolders = [];
62
+
63
+ entries.forEach(entry => {
64
+ // If entry starts with / or ./, it's a root folder
65
+ if (entry.startsWith('/') || entry.startsWith('./')) {
66
+ rootFolders.push(entry);
67
+ } else {
68
+ // Otherwise, it's a nested folder
69
+ nestedFolders.push(entry);
70
+ }
71
+ });
72
+
73
+ return { rootFolders, nestedFolders };
74
+ } catch (e) {
75
+ // If the file doesn't exist or can't be read, return empty arrays
76
+ return { rootFolders: [], nestedFolders: [] };
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Creates a regex pattern to ignore specified folders.
82
+ * The pattern will match paths where the specified folders appear as complete path segments.
83
+ * Special regex characters in folder names are automatically escaped.
84
+ * @param {Object|string[]} options - Options object
85
+ * @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path
86
+ * @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level
87
+ * @returns {RegExp} - Regex pattern to ignore the specified folders
88
+ */
89
+ function createIgnoreFoldersRegex(options) {
90
+ const nestedFolders = options.nestedFolders || [];
91
+ const rootFolders = options.rootFolders || [];
92
+
93
+ if (!Array.isArray(nestedFolders) || nestedFolders.length === 0) {
94
+ throw new Error('nestedFolders must be a non-empty array');
95
+ }
96
+
97
+ // If rootFolders is not provided or empty, use the original behavior
98
+ if (!rootFolders || !Array.isArray(rootFolders) || rootFolders.length === 0) {
99
+ // Escape special regex characters in folder names
100
+ const escapedFolders = nestedFolders.map(folder =>
101
+ folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
102
+ );
103
+
104
+ // Join folder names with | for the regex pattern
105
+ const foldersPattern = escapedFolders.join('|');
106
+
107
+ // Create a regex that matches paths where the specified folders appear as complete path segments
108
+ // Format: /(^|\/)(folder1|folder2|folder3)(\/|$)/
109
+ return new RegExp(`(^|\\/)(${foldersPattern})(\\/|$)`);
110
+ }
111
+
112
+ // Handle both rootFolders and nestedFolders
113
+ // Escape special regex characters in folder names
114
+ const escapedNestedFolders = nestedFolders.map(folder =>
115
+ folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
116
+ );
117
+
118
+ const escapedRootFolders = rootFolders.map(folder =>
119
+ folder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
120
+ );
121
+
122
+ // Join folder names with | for the regex patterns
123
+ const nestedFoldersPattern = escapedNestedFolders.join('|');
124
+ const rootFoldersPattern = escapedRootFolders.join('|');
125
+
126
+ // Create a regex that matches:
127
+ // 1. Root folders at the beginning of the path: /^(folderRootOnly)(\/|$)/
128
+ // 2. Nested folders anywhere in the path: /(^|\/)(folderAny1|folderAny2)(\/|$)/
129
+ const pattern = `^(${rootFoldersPattern})(\\/|$)|(^|\\/)(${nestedFoldersPattern})(\\/|$)`;
130
+ return new RegExp(pattern);
131
+ }
132
+
133
+ /**
134
+ * Creates a glob config array for ignoring specified folders.
135
+ * For nested folders, the pattern will be "**/" + folder + "/**".
136
+ * For root folders, the pattern will be folder + "/**".
137
+ * @param {Object} options - Options object
138
+ * @param {string[]} [options.nestedFolders] - Array of folder names to ignore anywhere in the path
139
+ * @param {string[]} [options.rootFolders] - Array of folder names that should only match at the root level
140
+ * @returns {string[]} - Array of glob patterns to ignore the specified folders
141
+ */
142
+ function createIgnoreGlobConfig(options = {}) {
143
+ const nestedFolders = options.nestedFolders || [];
144
+ const rootFolders = options.rootFolders || [];
145
+ const globPatterns = [];
146
+
147
+ // Create glob patterns for nested folders: **/{nestedFolder}/**
148
+ if (Array.isArray(nestedFolders) && nestedFolders.length > 0) {
149
+ nestedFolders.forEach(folder => {
150
+ // Remove leading ./ or / if present
151
+ const cleanFolder = folder.replace(/^(\.\/|\/)/g, '');
152
+ globPatterns.push(`**/${cleanFolder}/**`);
153
+ });
154
+ }
155
+
156
+ // Create glob patterns for root folders: {rootFolder}/**
157
+ if (Array.isArray(rootFolders) && rootFolders.length > 0) {
158
+ rootFolders.forEach(folder => {
159
+ // Remove leading ./ or / if present
160
+ const cleanFolder = folder.replace(/^(\.\/|\/)/g, '');
161
+ globPatterns.push(`${cleanFolder}/**`);
162
+ });
163
+ }
164
+
165
+ return globPatterns;
166
+ }
167
+
168
+ module.exports = {
169
+ createIgnoreFoldersRegex,
170
+ getMeteorIgnoreEntries,
171
+ createIgnoreGlobConfig,
172
+ };
package/lib/test.js ADDED
@@ -0,0 +1,61 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { createIgnoreFoldersRegex } = require("./ignore.js");
4
+
5
+ /**
6
+ * Generates eager test files dynamically
7
+ * @param {Object} options - Options for generating the test file
8
+ * @param {boolean} options.isAppTest - Whether this is an app test
9
+ * @param {string} options.projectDir - The project directory
10
+ * @param {string} options.buildContext - The build context
11
+ * @param {string} options.rootFolders
12
+ * @param {string} options.nestedFolders
13
+ * @returns {string} The path to the generated file
14
+ */
15
+ const generateEagerTestFile = ({
16
+ isAppTest,
17
+ projectDir,
18
+ buildContext,
19
+ rootFolders,
20
+ nestedFolders,
21
+ }) => {
22
+ const distDir = path.resolve(projectDir, ".meteor/local/test");
23
+ if (!fs.existsSync(distDir)) {
24
+ fs.mkdirSync(distDir, { recursive: true });
25
+ }
26
+
27
+ const excludeFoldersRegex = createIgnoreFoldersRegex({
28
+ nestedFolders: [
29
+ "node_modules",
30
+ ".meteor",
31
+ "public",
32
+ "private",
33
+ buildContext,
34
+ ...nestedFolders,
35
+ ],
36
+ rootFolders,
37
+ });
38
+
39
+ const filename = isAppTest ? "eager-app-tests.mjs" : "eager-tests.mjs";
40
+ const filePath = path.resolve(distDir, filename);
41
+ const regExp = isAppTest
42
+ ? "/\\.app-(?:test|spec)s?\\.[^.]+$/"
43
+ : "/\\.(?:test|spec)s?\\.[^.]+$/";
44
+
45
+ const content = `{
46
+ const ctx = import.meta.webpackContext('/', {
47
+ recursive: true,
48
+ regExp: ${regExp},
49
+ exclude: ${excludeFoldersRegex.toString()},
50
+ mode: 'eager',
51
+ });
52
+ ctx.keys().forEach(ctx);
53
+ }`;
54
+
55
+ fs.writeFileSync(filePath, content);
56
+ return filePath;
57
+ };
58
+
59
+ module.exports = {
60
+ generateEagerTestFile,
61
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/rspack.config.js CHANGED
@@ -8,6 +8,8 @@ const { cleanOmittedPaths, mergeSplitOverlap } = require("./lib/mergeRulesSplitO
8
8
  const { getMeteorAppSwcConfig } = require('./lib/swc.js');
9
9
  const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js');
10
10
  const { RequireExternalsPlugin } = require('./plugins/RequireExtenalsPlugin.js');
11
+ const { generateEagerTestFile } = require("./lib/test.js");
12
+ const { getMeteorIgnoreEntries, createIgnoreGlobConfig } = require("./lib/ignore");
11
13
 
12
14
  // Safe require that doesn't throw if the module isn't found
13
15
  function safeRequire(moduleName) {
@@ -101,11 +103,6 @@ function keepOutsideBuild() {
101
103
  };
102
104
  }
103
105
 
104
- // Watch options shared across both builds
105
- const defaultWatchOptions = {
106
- ignored: ['**/.meteor/local/**', '**/dist/**'],
107
- };
108
-
109
106
  /**
110
107
  * @param {{ isClient: boolean; isServer: boolean; isDevelopment?: boolean; isProduction?: boolean; isTest?: boolean }} Meteor
111
108
  * @param {{ mode?: string; clientEntry?: string; serverEntry?: string; clientOutputFolder?: string; serverOutputFolder?: string; chunksContext?: string; assetsContext?: string; serverAssetsContext?: string }} argv
@@ -137,7 +134,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
137
134
  const swcExternalHelpers = !!Meteor.swcExternalHelpers;
138
135
  const isNative = !!Meteor.isNative;
139
136
  const mode = isProd ? 'production' : 'development';
140
- const projectConfigPath = Meteor.projectConfigPath || path.resolve(process.cwd(), 'rspack.config.js');
137
+ const projectDir = process.cwd();
138
+ const projectConfigPath = Meteor.projectConfigPath || path.resolve(projectDir, 'rspack.config.js');
141
139
 
142
140
  const isTypescriptEnabled = Meteor.isTypescriptEnabled || false;
143
141
  const isJsxEnabled =
@@ -162,8 +160,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
162
160
  const bannerOutput = JSON.parse(Meteor.bannerOutput || process.env.RSPACK_BANNER || '""');
163
161
 
164
162
  // Determine output directories
165
- const clientOutputDir = path.resolve(process.cwd(), 'public');
166
- const serverOutputDir = path.resolve(process.cwd(), 'private');
163
+ const clientOutputDir = path.resolve(projectDir, 'public');
164
+ const serverOutputDir = path.resolve(projectDir, 'private');
167
165
 
168
166
  // Determine context for bundles and assets
169
167
  const buildContext = Meteor.buildContext || '_build';
@@ -171,7 +169,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
171
169
  const chunksContext = Meteor.chunksContext || 'build-chunks';
172
170
 
173
171
  // Determine build output and pass to Meteor
174
- const buildOutputDir = path.resolve(process.cwd(), buildContext, outputDir);
172
+ const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
175
173
  Meteor.buildOutputDir = buildOutputDir;
176
174
 
177
175
  // Add HtmlRspackPlugin function to Meteor
@@ -196,18 +194,24 @@ module.exports = async function (inMeteor = {}, argv = {}) {
196
194
  });
197
195
  };
198
196
 
199
- // Set watch options
197
+ // Get Meteor ignore entries
198
+ const { rootFolders, nestedFolders } = getMeteorIgnoreEntries(projectDir);
199
+
200
+ // Set default watch options
200
201
  const watchOptions = {
201
- ...defaultWatchOptions,
202
- ...(isTest &&
203
- isTestEager && {
204
- ignored: [
205
- ...defaultWatchOptions.ignored,
206
- `**/${buildContext}/**`,
207
- '**/.meteor/local/**',
208
- '**/node_modules/**',
202
+ ignored: [
203
+ ...createIgnoreGlobConfig({
204
+ rootFolders,
205
+ nestedFolders: [
206
+ ".meteor/local",
207
+ "dist",
208
+ ...(isTest && isTestEager
209
+ ? [buildContext, ".meteor/local", "node_modules"]
210
+ : []),
211
+ ...(nestedFolders || []),
209
212
  ],
210
213
  }),
214
+ ],
211
215
  };
212
216
 
213
217
  if (Meteor.isDebug || Meteor.isVerbose) {
@@ -368,12 +372,25 @@ module.exports = async function (inMeteor = {}, argv = {}) {
368
372
  experiments: { css: true },
369
373
  };
370
374
 
375
+
371
376
  const serverEntry =
372
377
  isTest && isTestEager && isTestFullApp
373
- ? path.resolve(process.cwd(), 'node_modules/@meteorjs/rspack/entries/eager-app-tests.mjs')
378
+ ? generateEagerTestFile({
379
+ isAppTest: true,
380
+ projectDir,
381
+ buildContext,
382
+ rootFolders,
383
+ nestedFolders,
384
+ })
374
385
  : isTest && isTestEager
375
- ? path.resolve(process.cwd(), 'node_modules/@meteorjs/rspack/entries/eager-tests.mjs')
376
- : path.resolve(process.cwd(), buildContext, entryPath);
386
+ ? generateEagerTestFile({
387
+ isAppTest: false,
388
+ projectDir,
389
+ buildContext,
390
+ rootFolders,
391
+ nestedFolders,
392
+ })
393
+ : path.resolve(projectDir, buildContext, entryPath);
377
394
  const serverNameConfig = `[${(isTest && 'test-') || ''}${
378
395
  (isTestModule && 'module') || 'server'
379
396
  }-rspack]`;
@@ -404,7 +421,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
404
421
  resolve: {
405
422
  extensions,
406
423
  alias,
407
- modules: ['node_modules', path.resolve(process.cwd())],
424
+ modules: ['node_modules', path.resolve(projectDir)],
408
425
  conditionNames: ['import', 'require', 'node', 'default'],
409
426
  },
410
427
  externals,
@@ -437,7 +454,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
437
454
 
438
455
  // Load and apply project-level overrides for the selected build
439
456
  // Check if we're in a Meteor package directory by looking at the path
440
- const isMeteorPackageConfig = process.cwd().includes('/packages/rspack');
457
+ const isMeteorPackageConfig = projectDir.includes('/packages/rspack');
441
458
  if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
442
459
  // Check if there's a .mjs or .cjs version of the config file
443
460
  const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
@@ -1,9 +0,0 @@
1
- {
2
- const ctx = import.meta.webpackContext('/', {
3
- recursive: true,
4
- regExp: /\.app-(?:test|spec)s?\.[^.]+$/,
5
- exclude: /(^|\/)(node_modules|\.meteor|_build)(\/|$)/,
6
- mode: 'eager',
7
- });
8
- ctx.keys().forEach(ctx);
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- const ctx = import.meta.webpackContext('/', {
3
- recursive: true,
4
- regExp: /\.(?:test|spec)s?\.[^.]+$/,
5
- exclude: /(^|\/)(node_modules|\.meteor|_build)(\/|$)/,
6
- mode: 'eager',
7
- });
8
- ctx.keys().forEach(ctx);
9
- }