@meteorjs/rspack 0.0.0
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/package.json +18 -0
- package/plugins/RequireExtenalsPlugin.js +264 -0
- package/rspack.config.js +286 -0
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meteorjs/rspack",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Configuration logic for using Rspack in Meteor projects",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "",
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@rspack/plugin-react-refresh": "^1.4.3",
|
|
11
|
+
"ignore-loader": "^0.1.2",
|
|
12
|
+
"webpack-merge": "^6.0.1"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@rspack/cli": ">=1.3.0",
|
|
16
|
+
"@rspack/core": ">=1.3.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// RequireExternalsPlugin.js
|
|
2
|
+
//
|
|
3
|
+
// This plugin prepare the require of externals used to be lazy required by Meteor bundler.
|
|
4
|
+
//
|
|
5
|
+
// It can describe additional externals using the externals option by array, RegExp or function.
|
|
6
|
+
// These externals will be lazy required as well, and optionally could be resolved using
|
|
7
|
+
// the externalMap function if provided.
|
|
8
|
+
// Used for Blaze to translate require of html files to require of js files bundled by Meteor.
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
export class RequireExternalsPlugin {
|
|
14
|
+
constructor({
|
|
15
|
+
filePath,
|
|
16
|
+
// Externals can be:
|
|
17
|
+
// - An array of strings: module name must be included in the array
|
|
18
|
+
// - A RegExp: module name must match the regex
|
|
19
|
+
// - A function: function(name) must return true for the module name
|
|
20
|
+
externals = null,
|
|
21
|
+
// ExternalMap is a function that receives the request object and returns the external request path
|
|
22
|
+
// It can be used to customize how external modules are mapped to file paths
|
|
23
|
+
// If not provided, the default behavior is to map the external module name.
|
|
24
|
+
externalMap = null,
|
|
25
|
+
} = {}) {
|
|
26
|
+
this.pluginName = 'RequireExternalsPlugin';
|
|
27
|
+
|
|
28
|
+
// Prepare externals
|
|
29
|
+
this._externals = externals;
|
|
30
|
+
this._externalMap = externalMap;
|
|
31
|
+
this._defaultExternalPrefix = 'external ';
|
|
32
|
+
|
|
33
|
+
// Prepare paths
|
|
34
|
+
this.filePath = path.resolve(process.cwd(), filePath);
|
|
35
|
+
this.backRoot = '../'.repeat(
|
|
36
|
+
filePath.replace(/^\.?\/+/, '').split('/').length - 1
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Initialize funcCount based on existing helpers in the file
|
|
40
|
+
this._funcCount = this._computeNextFuncCount();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Helper method to check if a module name matches the externals or default prefix
|
|
44
|
+
_isExternalModule(name) {
|
|
45
|
+
if (typeof name !== 'string') return false;
|
|
46
|
+
|
|
47
|
+
// Check externals if provided
|
|
48
|
+
if (this._externals) {
|
|
49
|
+
// If externals is an array, use includes method
|
|
50
|
+
if (Array.isArray(this._externals)) {
|
|
51
|
+
if (this._externals.includes(name)) {
|
|
52
|
+
return { isExternal: true, type: 'externals', value: name };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// If externals is a RegExp, use test method
|
|
56
|
+
else if (this._externals instanceof RegExp) {
|
|
57
|
+
if (this._externals.test(name)) {
|
|
58
|
+
return { isExternal: true, type: 'externals', value: name };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// If externals is a function, call it with the name
|
|
62
|
+
else if (typeof this._externals === 'function') {
|
|
63
|
+
if (this._externals(name)) {
|
|
64
|
+
return { isExternal: true, type: 'externals', value: name };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (name.startsWith(this._defaultExternalPrefix)) {
|
|
70
|
+
return { isExternal: true, type: 'prefix', value: name };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { isExternal: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Helper method to extract package name from module name
|
|
77
|
+
_extractPackageName(name) {
|
|
78
|
+
let pkg = name.slice(this._defaultExternalPrefix.length);
|
|
79
|
+
if (pkg.startsWith('"') && pkg.endsWith('"')) pkg = pkg.slice(1, -1);
|
|
80
|
+
|
|
81
|
+
// If the extracted package name is a path, use the path as is
|
|
82
|
+
if (
|
|
83
|
+
pkg &&
|
|
84
|
+
(path.isAbsolute(pkg) || pkg.startsWith('./') || pkg.startsWith('../'))
|
|
85
|
+
) {
|
|
86
|
+
const module = this.externalsMeta.get(pkg);
|
|
87
|
+
if (module) {
|
|
88
|
+
return `${this.backRoot}${module.relativeRequest}`;
|
|
89
|
+
}
|
|
90
|
+
return `${this.backRoot}${name}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return pkg;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
apply(compiler) {
|
|
97
|
+
// Initialize externalsMeta if it doesn't exist
|
|
98
|
+
this.externalsMeta = this.externalsMeta || new Map();
|
|
99
|
+
|
|
100
|
+
// Only set compiler.options.externals if both externals and externalMap are defined
|
|
101
|
+
if (this._externals && this._externalMap) {
|
|
102
|
+
compiler.options.externals = [
|
|
103
|
+
...compiler.options.externals || [],
|
|
104
|
+
(module, callback) => {
|
|
105
|
+
const { request, context } = module;
|
|
106
|
+
const matchInfo = this._isExternalModule(request);
|
|
107
|
+
if (matchInfo.isExternal) {
|
|
108
|
+
|
|
109
|
+
let externalRequest;
|
|
110
|
+
// Use externalMap function if provided
|
|
111
|
+
if (this._externalMap && typeof this._externalMap === 'function') {
|
|
112
|
+
externalRequest = this._externalMap(module);
|
|
113
|
+
|
|
114
|
+
const relContext = path.relative(process.cwd(), context);
|
|
115
|
+
// Store the original request to resolve properly the lazy html require later
|
|
116
|
+
this.externalsMeta.set(externalRequest, {
|
|
117
|
+
originalRequest: request,
|
|
118
|
+
externalRequest,
|
|
119
|
+
relativeRequest: path.join(relContext, request),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// tell Rspack "don't bundle this, import it at runtime"
|
|
123
|
+
return callback(null, externalRequest);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
callback(); // otherwise normal resolution
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
compiler.hooks.done.tap({ name: this.pluginName, stage: -10 }, (stats) => {
|
|
133
|
+
// 1) Ensure globalThis.module / exports block is present
|
|
134
|
+
this._ensureGlobalThisModule();
|
|
135
|
+
|
|
136
|
+
// 2) Re-load existing requires from disk on every run
|
|
137
|
+
const existing = this._readExistingRequires();
|
|
138
|
+
|
|
139
|
+
// 2a) Compute the *current* externals in this build
|
|
140
|
+
const info = stats.toJson({ modules: true });
|
|
141
|
+
const current = new Set();
|
|
142
|
+
for (const m of info.modules) {
|
|
143
|
+
const matchInfo = this._isExternalModule(m.name);
|
|
144
|
+
if (matchInfo.isExternal) {
|
|
145
|
+
const pkg = this._extractPackageName(m.name, matchInfo);
|
|
146
|
+
if (pkg) {
|
|
147
|
+
current.add(pkg);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2b) Remove any requires that are no longer in `current`
|
|
153
|
+
const toRemove = [...existing].filter(p => !current.has(p));
|
|
154
|
+
if (toRemove.length) {
|
|
155
|
+
let content = fs.readFileSync(this.filePath, 'utf-8');
|
|
156
|
+
|
|
157
|
+
// Strip stale require(...) lines
|
|
158
|
+
for (const pkg of toRemove) {
|
|
159
|
+
const re = new RegExp(`^.*require\\('${pkg}'\\);?.*(\\r?\\n)?`, 'gm');
|
|
160
|
+
content = content.replace(re, '');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Strip out any now-empty helper functions:
|
|
164
|
+
// function lazyExternalImportsX() {
|
|
165
|
+
// }
|
|
166
|
+
const emptyFnRe = /^function\s+lazyExternalImports\d+\s*\(\)\s*{\s*}\s*(\r?\n)?/gm;
|
|
167
|
+
content = content.replace(emptyFnRe, '');
|
|
168
|
+
|
|
169
|
+
// Write the cleaned file back
|
|
170
|
+
fs.writeFileSync(this.filePath, content, 'utf-8');
|
|
171
|
+
|
|
172
|
+
// Re-populate `existing` so the add-diff is accurate
|
|
173
|
+
existing.clear();
|
|
174
|
+
for (const match of content.matchAll(/require\('([^']+)'\)/g)) {
|
|
175
|
+
existing.add(match[1]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 3) Collect any new externals from this build
|
|
180
|
+
const newRequires = [];
|
|
181
|
+
for (const module of info.modules) {
|
|
182
|
+
const name = module.name;
|
|
183
|
+
const matchInfo = this._isExternalModule(name);
|
|
184
|
+
if (!matchInfo.isExternal) continue;
|
|
185
|
+
|
|
186
|
+
const pkg = this._extractPackageName(name, matchInfo);
|
|
187
|
+
if (pkg && !existing.has(pkg)) {
|
|
188
|
+
existing.add(pkg);
|
|
189
|
+
newRequires.push(`require('${pkg}')`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 4) Append new imports if any
|
|
194
|
+
if (newRequires.length) {
|
|
195
|
+
const fnName = `lazyExternalImports${this._funcCount++}`;
|
|
196
|
+
const body = newRequires.map(req => ` ${req};`).join('\n');
|
|
197
|
+
const fnCode = `\nfunction ${fnName}() {\n${body}\n}\n`;
|
|
198
|
+
try {
|
|
199
|
+
fs.appendFileSync(this.filePath, fnCode);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error(`Failed to append imports to ${this.filePath}:`, err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_computeNextFuncCount() {
|
|
208
|
+
let max = 0;
|
|
209
|
+
if (fs.existsSync(this.filePath)) {
|
|
210
|
+
try {
|
|
211
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
212
|
+
const fnRe = /function\s+lazyExternalImports(\d+)\s*\(\)/g;
|
|
213
|
+
let match;
|
|
214
|
+
while ((match = fnRe.exec(content)) !== null) {
|
|
215
|
+
const n = parseInt(match[1], 10);
|
|
216
|
+
if (n > max) max = n;
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// ignore read errors
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// next count is max found plus one
|
|
223
|
+
return max + 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_ensureGlobalThisModule() {
|
|
227
|
+
const block = [
|
|
228
|
+
`/* Polyfill globalThis.module & exports */`,
|
|
229
|
+
`if (typeof globalThis.module === 'undefined') {`,
|
|
230
|
+
` globalThis.module = { exports: {} };`,
|
|
231
|
+
`}`,
|
|
232
|
+
`if (typeof globalThis.exports === 'undefined') {`,
|
|
233
|
+
` globalThis.exports = globalThis.module.exports;`,
|
|
234
|
+
`}`
|
|
235
|
+
].join('\n') + '\n';
|
|
236
|
+
|
|
237
|
+
let content = '';
|
|
238
|
+
if (fs.existsSync(this.filePath)) {
|
|
239
|
+
content = fs.readFileSync(this.filePath, 'utf-8');
|
|
240
|
+
if (!content.includes(`typeof globalThis.module === 'undefined'`)) {
|
|
241
|
+
// Prepend so it lives at the very top
|
|
242
|
+
fs.writeFileSync(this.filePath, content + '\n' + block, 'utf-8');
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
// File doesn’t exist yet: create with just the block
|
|
246
|
+
fs.writeFileSync(this.filePath, block, 'utf-8');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_readExistingRequires() {
|
|
251
|
+
const existing = new Set();
|
|
252
|
+
try {
|
|
253
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
254
|
+
const requireRegex = /require\('([^']+)'\)/g;
|
|
255
|
+
let match;
|
|
256
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
257
|
+
existing.add(match[1]);
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// ignore if file missing or unreadable
|
|
261
|
+
}
|
|
262
|
+
return existing;
|
|
263
|
+
}
|
|
264
|
+
}
|
package/rspack.config.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { DefinePlugin, BannerPlugin } from '@rspack/core';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { merge } from 'webpack-merge';
|
|
6
|
+
|
|
7
|
+
import { RequireExternalsPlugin } from './plugins/RequireExtenalsPlugin.js';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
// Safe require that doesn't throw if the module isn't found
|
|
12
|
+
function safeRequire(moduleName) {
|
|
13
|
+
try {
|
|
14
|
+
return require(moduleName);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (
|
|
17
|
+
error.code === 'MODULE_NOT_FOUND' &&
|
|
18
|
+
error.message.includes(moduleName)
|
|
19
|
+
) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
throw error; // rethrow if it's a different error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Persistent filesystem cache strategy
|
|
27
|
+
function createCacheStrategy(mode) {
|
|
28
|
+
return {
|
|
29
|
+
cache: true,
|
|
30
|
+
experiments: {
|
|
31
|
+
cache: {
|
|
32
|
+
version: `swc-${mode}`,
|
|
33
|
+
type: 'persistent',
|
|
34
|
+
storage: {
|
|
35
|
+
type: 'filesystem',
|
|
36
|
+
directory: 'node_modules/.cache/rspack',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// SWC loader rule (JSX/JS)
|
|
44
|
+
function createSwcConfig({ isRun }) {
|
|
45
|
+
return {
|
|
46
|
+
test: /\.[jt]sx?$/,
|
|
47
|
+
exclude: /node_modules|\.meteor\/local/,
|
|
48
|
+
loader: 'builtin:swc-loader',
|
|
49
|
+
options: {
|
|
50
|
+
jsc: {
|
|
51
|
+
baseUrl: process.cwd(),
|
|
52
|
+
paths: { '/*': ['*'] },
|
|
53
|
+
parser: { syntax: 'ecmascript', jsx: true },
|
|
54
|
+
target: 'es2015',
|
|
55
|
+
transform: {
|
|
56
|
+
react: {
|
|
57
|
+
development: isRun,
|
|
58
|
+
refresh: isRun,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Watch options shared across both builds
|
|
67
|
+
const watchOptions = {
|
|
68
|
+
ignored: ['**/main.html', '**/dist/**', '**/.meteor/local/**'],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {{ isClient: boolean; isServer: boolean; isDevelopment?: boolean; isProduction?: boolean; isTest?: boolean }} Meteor
|
|
73
|
+
* @param {{ mode?: string; clientEntry?: string; serverEntry?: string; clientOutputFolder?: string; serverOutputFolder?: string; bundlesContext?: string; assetsContext?: string; serverAssetsContext?: string }} argv
|
|
74
|
+
* @returns {import('@rspack/cli').Configuration[]}
|
|
75
|
+
*/
|
|
76
|
+
export default function (inMeteor = {}, argv = {}) {
|
|
77
|
+
// Transform Meteor env properties to proper boolean values
|
|
78
|
+
const Meteor = { ...inMeteor };
|
|
79
|
+
// Convert string boolean values to actual booleans
|
|
80
|
+
for (const key in Meteor) {
|
|
81
|
+
if (Meteor[key] === 'true' || Meteor[key] === true) {
|
|
82
|
+
Meteor[key] = true;
|
|
83
|
+
} else if (Meteor[key] === 'false' || Meteor[key] === false) {
|
|
84
|
+
Meteor[key] = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const isProd = Meteor.isProduction || argv.mode === 'production';
|
|
89
|
+
const isDev = Meteor.isDevelopment || !isProd;
|
|
90
|
+
const isTest = Meteor.isTest;
|
|
91
|
+
const isClient = Meteor.isClient;
|
|
92
|
+
const isRun = Meteor.isRun;
|
|
93
|
+
const isReactEnabled = Meteor.isReactEnabled;
|
|
94
|
+
const mode = isProd ? 'production' : 'development';
|
|
95
|
+
|
|
96
|
+
// Determine entry points
|
|
97
|
+
const entryPath = Meteor.entryPath;
|
|
98
|
+
|
|
99
|
+
// Determine output points
|
|
100
|
+
const outputPath = Meteor.outputPath;
|
|
101
|
+
const outputFilename = Meteor.outputFilename;
|
|
102
|
+
|
|
103
|
+
// Determine run point
|
|
104
|
+
const runPath = Meteor.runPath;
|
|
105
|
+
|
|
106
|
+
// Determine banner
|
|
107
|
+
const bannerOutput = JSON.parse(Meteor.bannerOutput || '');
|
|
108
|
+
|
|
109
|
+
// Determine output directories
|
|
110
|
+
const clientOutputDir = path.resolve(process.cwd(), 'public');
|
|
111
|
+
const serverOutputDir = path.resolve(process.cwd(), 'server');
|
|
112
|
+
|
|
113
|
+
// Determine context for bundles and assets
|
|
114
|
+
const buildContext = Meteor.buildContext || '_rspack';
|
|
115
|
+
const bundlesContext = Meteor.bundlesContext || 'bundles';
|
|
116
|
+
const assetsContext = Meteor.assetsContext || 'assets';
|
|
117
|
+
|
|
118
|
+
if (Meteor.isDebug) {
|
|
119
|
+
console.log('[i] Rspack mode:', mode);
|
|
120
|
+
console.log('[i] Meteor flags:', Meteor);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Base client config
|
|
124
|
+
let clientConfig = {
|
|
125
|
+
name: 'meteor-client',
|
|
126
|
+
target: 'web',
|
|
127
|
+
mode,
|
|
128
|
+
entry: path.resolve(process.cwd(), buildContext, entryPath),
|
|
129
|
+
output: {
|
|
130
|
+
path: clientOutputDir,
|
|
131
|
+
filename: () =>
|
|
132
|
+
isDev ? outputFilename : `../${buildContext}/${outputPath}`,
|
|
133
|
+
libraryTarget: 'commonjs',
|
|
134
|
+
publicPath: '/',
|
|
135
|
+
chunkFilename: `${bundlesContext}/[id].[chunkhash].js`,
|
|
136
|
+
assetModuleFilename: `${assetsContext}/[hash][ext][query]`,
|
|
137
|
+
},
|
|
138
|
+
optimization: {
|
|
139
|
+
usedExports: true,
|
|
140
|
+
splitChunks: { chunks: 'async' },
|
|
141
|
+
},
|
|
142
|
+
module: {
|
|
143
|
+
rules: [
|
|
144
|
+
createSwcConfig({ isRun }),
|
|
145
|
+
...(Meteor.isBlazeEnabled
|
|
146
|
+
? [
|
|
147
|
+
{
|
|
148
|
+
test: /\.html$/,
|
|
149
|
+
loader: 'ignore-loader',
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
: []),
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
resolve: { extensions: ['.js', '.jsx', '.json'] },
|
|
156
|
+
externals: [/^(meteor.*|react$|react-dom$)/],
|
|
157
|
+
plugins: [
|
|
158
|
+
...(isRun
|
|
159
|
+
? [
|
|
160
|
+
...(isReactEnabled
|
|
161
|
+
? [new (safeRequire('@rspack/plugin-react-refresh'))()]
|
|
162
|
+
: []),
|
|
163
|
+
new RequireExternalsPlugin({
|
|
164
|
+
filePath: path.join(buildContext, runPath),
|
|
165
|
+
...(Meteor.isBlazeEnabled && {
|
|
166
|
+
externals: /\.html$/,
|
|
167
|
+
externalMap: (module) => {
|
|
168
|
+
const { request, context } = module;
|
|
169
|
+
if (request.endsWith('.html')) {
|
|
170
|
+
const relContext = path.relative(process.cwd(), context);
|
|
171
|
+
const { name } = path.parse(request);
|
|
172
|
+
return `./${relContext}/template.${name}.js`;
|
|
173
|
+
}
|
|
174
|
+
return request;
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
}),
|
|
178
|
+
].filter(Boolean)
|
|
179
|
+
: []),
|
|
180
|
+
new DefinePlugin({
|
|
181
|
+
'Meteor.isClient': JSON.stringify(true),
|
|
182
|
+
'Meteor.isServer': JSON.stringify(false),
|
|
183
|
+
'Meteor.isTest': JSON.stringify(isTest),
|
|
184
|
+
'Meteor.isDevelopment': JSON.stringify(isDev),
|
|
185
|
+
'Meteor.isProduction': JSON.stringify(isProd),
|
|
186
|
+
}),
|
|
187
|
+
new BannerPlugin({
|
|
188
|
+
banner: bannerOutput,
|
|
189
|
+
entryOnly: true,
|
|
190
|
+
}),
|
|
191
|
+
],
|
|
192
|
+
watchOptions,
|
|
193
|
+
devtool: isDev ? 'source-map' : 'hidden-source-map',
|
|
194
|
+
...(isRun && {
|
|
195
|
+
devServer: {
|
|
196
|
+
static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
|
|
197
|
+
hot: true,
|
|
198
|
+
liveReload: true,
|
|
199
|
+
...(Meteor.isBlazeEnabled && { hot: false }),
|
|
200
|
+
port: 3005,
|
|
201
|
+
devMiddleware: {
|
|
202
|
+
writeToDisk: false,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
experiments: { incremental: true },
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Base server config
|
|
210
|
+
let serverConfig = {
|
|
211
|
+
name: 'meteor-server',
|
|
212
|
+
target: 'node',
|
|
213
|
+
mode,
|
|
214
|
+
entry: path.resolve(process.cwd(), buildContext, entryPath),
|
|
215
|
+
output: {
|
|
216
|
+
path: serverOutputDir,
|
|
217
|
+
filename: () => `../${buildContext}/${outputPath}`,
|
|
218
|
+
libraryTarget: 'commonjs',
|
|
219
|
+
chunkFilename: `${bundlesContext}/[id].[chunkhash].js`,
|
|
220
|
+
assetModuleFilename: `${assetsContext}/[hash][ext][query]`,
|
|
221
|
+
},
|
|
222
|
+
optimization: { usedExports: true },
|
|
223
|
+
module: {
|
|
224
|
+
rules: [
|
|
225
|
+
{
|
|
226
|
+
test: /\.meteor\/local/,
|
|
227
|
+
use: 'builtin:empty-loader',
|
|
228
|
+
sideEffects: false,
|
|
229
|
+
},
|
|
230
|
+
createSwcConfig({ isRun }),
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
resolve: {
|
|
234
|
+
extensions: ['.js', '.jsx', '.json'],
|
|
235
|
+
modules: ['node_modules', path.resolve(process.cwd())],
|
|
236
|
+
conditionNames: ['import', 'require', 'node', 'default'],
|
|
237
|
+
},
|
|
238
|
+
externals: [/^(meteor.*|react|react-dom)/],
|
|
239
|
+
plugins: [
|
|
240
|
+
new DefinePlugin({
|
|
241
|
+
'Meteor.isClient': JSON.stringify(false),
|
|
242
|
+
'Meteor.isServer': JSON.stringify(true),
|
|
243
|
+
'Meteor.isTest': JSON.stringify(isTest),
|
|
244
|
+
'Meteor.isDevelopment': JSON.stringify(isDev),
|
|
245
|
+
'Meteor.isProduction': JSON.stringify(isProd),
|
|
246
|
+
}),
|
|
247
|
+
new BannerPlugin({
|
|
248
|
+
banner: bannerOutput,
|
|
249
|
+
entryOnly: true,
|
|
250
|
+
}),
|
|
251
|
+
],
|
|
252
|
+
watchOptions,
|
|
253
|
+
devtool: isRun ? 'source-map' : 'hidden-source-map',
|
|
254
|
+
...(isRun &&
|
|
255
|
+
merge(createCacheStrategy(mode), { experiments: { incremental: true } })),
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Load and apply project-level overrides for the selected build
|
|
259
|
+
const projectConfigPath = path.resolve(process.cwd(), 'rspack.config.js');
|
|
260
|
+
|
|
261
|
+
// Check if we're in a Meteor package directory by looking at the path
|
|
262
|
+
const isMeteorPackageConfig = process.cwd().includes('/packages/rspack');
|
|
263
|
+
if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
|
|
264
|
+
const projectConfig =
|
|
265
|
+
require(projectConfigPath)?.default || require(projectConfigPath);
|
|
266
|
+
|
|
267
|
+
const userConfig =
|
|
268
|
+
typeof projectConfig === 'function'
|
|
269
|
+
? projectConfig(Meteor, argv)
|
|
270
|
+
: projectConfig;
|
|
271
|
+
|
|
272
|
+
if (Meteor.isClient) {
|
|
273
|
+
clientConfig = merge(clientConfig, userConfig);
|
|
274
|
+
}
|
|
275
|
+
if (Meteor.isServer) {
|
|
276
|
+
serverConfig = merge(serverConfig, userConfig);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Return the appropriate configuration
|
|
281
|
+
if (isClient) {
|
|
282
|
+
return [clientConfig];
|
|
283
|
+
}
|
|
284
|
+
// Meteor.isServer
|
|
285
|
+
return [serverConfig];
|
|
286
|
+
}
|