@meteorjs/rspack 1.1.0-beta.8 → 2.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/.claude/settings.local.json +10 -0
- package/README.md +142 -0
- package/index.d.ts +51 -1
- package/lib/localDependenciesHelpers.js +184 -0
- package/lib/meteorRspackConfigHelpers.js +121 -0
- package/lib/meteorRspackHelpers.js +135 -3
- package/lib/swc.js +36 -6
- package/lib/test.js +8 -5
- package/package.json +10 -2
- package/plugins/MeteorRspackOutputPlugin.js +92 -1
- package/plugins/RequireExtenalsPlugin.js +10 -4
- package/rspack.config.js +301 -258
- package/scripts/bump-version.js +89 -0
- package/scripts/publish-beta.sh +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @meteorjs/rspack
|
|
2
|
+
|
|
3
|
+
The default [Rspack](https://rspack.dev) configuration for Meteor applications. This package provides everything you need to bundle your Meteor app with Rspack out of the box: client and server builds, SWC transpilation, React/Blaze/Angular support, hot module replacement, asset management, and all the Meteor-specific wiring so you don't have to.
|
|
4
|
+
|
|
5
|
+
When Meteor runs with the Rspack bundler enabled, this package is what generates the underlying Rspack configuration. It detects your project setup (TypeScript, React, Blaze, Angular), sets up the right loaders and plugins, defines `Meteor.isClient`/`Meteor.isServer` and friends, configures caching, and exposes a set of helpers you can use in your own `rspack.config.js` to customize the build without breaking Meteor integration.
|
|
6
|
+
|
|
7
|
+
## What it provides
|
|
8
|
+
|
|
9
|
+
- **Dual client/server builds** with the correct targets, externals, and output paths
|
|
10
|
+
- **SWC-based transpilation** for JS/TS/JSX/TSX with automatic framework detection
|
|
11
|
+
- **React Fast Refresh** in development when React is enabled
|
|
12
|
+
- **Blaze template handling** via ignore-loader when Blaze is enabled
|
|
13
|
+
- **Persistent filesystem caching** for fast rebuilds
|
|
14
|
+
- **Asset externals and HTML generation** through custom Rspack plugins
|
|
15
|
+
- **A `defineConfig` helper** that accepts a factory function receiving Meteor environment flags and build utilities
|
|
16
|
+
- **Customizable config** via `rspack.config.js` in your project root, with safe merging that warns if you try to override reserved settings
|
|
17
|
+
- **Automatic CSS delegation** when rspack is configured with CSS, Less, or SCSS loaders, Meteor automatically detects the handled extensions after the first compilation and stops processing those files itself in the entry folder context. No `.meteorignore` entries needed.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
[Rspack integration](https://docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html) is automatically managed by the rspack Atmosphere package.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
meteor add rspack
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
By doing this, your Meteor app will automatically serve `@meteorjs/rspack` and the required `@rspack/cli`, `@rspack/core`, among others.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
In your project's `rspack.config.js`, use the `defineConfig` helper to customize the build. The factory function receives a `env` object with Meteor environment flags and helper utilities:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
const { defineConfig } = require('@meteorjs/rspack');
|
|
35
|
+
|
|
36
|
+
module.exports = defineConfig((env, argv) => {
|
|
37
|
+
// env.isClient, env.isServer, env.isDevelopment, env.isProduction
|
|
38
|
+
// env.isReactEnabled, env.isBlazeEnabled, etc.
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
// Your custom Rspack configuration here.
|
|
42
|
+
// It gets safely merged with the Meteor defaults.
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
More information is available in the official docs: [Rspack Bundler Integration](https://docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html#custom-rspack-config-js).
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
### Install dependencies
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Version bumping
|
|
58
|
+
|
|
59
|
+
Use `npm run bump` to update the version in `package.json` before publishing.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm run bump -- <major|minor|patch> [--beta]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Standard bumps** increment the version and remove any prerelease suffix:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run bump -- patch # 1.0.1 -> 1.0.2
|
|
69
|
+
npm run bump -- minor # 1.0.1 -> 1.1.0
|
|
70
|
+
npm run bump -- major # 1.0.1 -> 2.0.0
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Beta bumps** append or increment a `-beta.N` prerelease suffix:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run bump -- patch --beta # 1.0.1 -> 1.0.2-beta.0
|
|
77
|
+
npm run bump -- patch --beta # 1.0.2-beta.0 -> 1.0.2-beta.1
|
|
78
|
+
npm run bump -- patch --beta # 1.0.2-beta.1 -> 1.0.2-beta.2
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If you change the bump level while on a beta, the base version updates and the beta counter resets:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm run bump -- minor --beta # 1.0.2-beta.2 -> 1.1.0-beta.0
|
|
85
|
+
npm run bump -- major --beta # 1.1.0-beta.0 -> 2.0.0-beta.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Publishing a beta release
|
|
89
|
+
|
|
90
|
+
After bumping to a beta version, publish to the `beta` dist-tag:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run bump -- patch --beta
|
|
94
|
+
npm run publish:beta
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Users can then install the beta with:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install @meteorjs/rspack@beta
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can pass extra flags to `npm publish` through the script:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run publish:beta -- --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Publishing an official release
|
|
110
|
+
|
|
111
|
+
After bumping to a stable version, publish with the default `latest` tag:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run bump -- patch
|
|
115
|
+
npm publish
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Typical workflows
|
|
119
|
+
|
|
120
|
+
**Beta iteration**: ship multiple beta builds for the same upcoming patch:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm run bump -- patch --beta # 1.0.1 -> 1.0.2-beta.0
|
|
124
|
+
npm run publish:beta
|
|
125
|
+
# ... fix issues ...
|
|
126
|
+
npm run bump -- patch --beta # 1.0.2-beta.0 -> 1.0.2-beta.1
|
|
127
|
+
npm run publish:beta
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Promote beta to stable**: once the beta is ready, bump to the stable version and publish:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm run bump -- patch # 1.0.2-beta.1 -> 1.0.3
|
|
134
|
+
npm publish
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Direct stable release**: skip the beta phase entirely:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run bump -- minor # 1.0.1 -> 1.1.0
|
|
141
|
+
npm publish
|
|
142
|
+
```
|
package/index.d.ts
CHANGED
|
@@ -29,12 +29,14 @@ type MeteorEnv = Record<string, any> & {
|
|
|
29
29
|
* A function that creates an instance of HtmlRspackPlugin with default options.
|
|
30
30
|
* @param options - Optional configuration options that will be merged with defaults
|
|
31
31
|
* @returns An instance of HtmlRspackPlugin
|
|
32
|
+
* @example Meteor.HtmlRspackPlugin({ title: 'My App' })
|
|
32
33
|
*/
|
|
33
34
|
HtmlRspackPlugin: (options?: HtmlRspackPluginOptions) => HtmlRspackPlugin;
|
|
34
35
|
/**
|
|
35
36
|
* Wrap externals for Meteor runtime.
|
|
36
37
|
* @param deps - Package names or module IDs
|
|
37
38
|
* @returns A config object with externals configuration
|
|
39
|
+
* @example ...Meteor.compileWithMeteor(['sharp', 'thread-stream'])
|
|
38
40
|
*/
|
|
39
41
|
compileWithMeteor: (deps: RuleSetConditions) => Record<string, object>;
|
|
40
42
|
/**
|
|
@@ -42,6 +44,7 @@ type MeteorEnv = Record<string, any> & {
|
|
|
42
44
|
* @param deps - Package names to include in SWC loader
|
|
43
45
|
* @param options - Optional configuration options
|
|
44
46
|
* @returns A config object with module rules configuration
|
|
47
|
+
* @example ...Meteor.compileWithRspack(['grubba-rpc', 'zod'])
|
|
45
48
|
*/
|
|
46
49
|
compileWithRspack: (deps: RuleSetConditions, options?: SwcLoaderOptions) => Record<string, object>;
|
|
47
50
|
/**
|
|
@@ -49,21 +52,35 @@ type MeteorEnv = Record<string, any> & {
|
|
|
49
52
|
* @param enabled - Whether to enable caching
|
|
50
53
|
* @param cacheConfig - Optional cache configuration
|
|
51
54
|
* @returns A config object with cache configuration
|
|
55
|
+
* @example ...Meteor.setCache(false)
|
|
52
56
|
*/
|
|
53
57
|
setCache: (enabled: boolean | 'memory') => Record<string, object>;
|
|
54
58
|
/**
|
|
55
59
|
* Enable Rspack split vendor chunk.
|
|
56
60
|
* @returns A config object with optimization configuration
|
|
61
|
+
* @example ...Meteor.splitVendorChunk()
|
|
57
62
|
*/
|
|
58
63
|
splitVendorChunk: () => Record<string, object>;
|
|
59
64
|
/**
|
|
60
|
-
* Extend
|
|
65
|
+
* Extend the SWC loader config by smart-merging custom options on top of
|
|
66
|
+
* Meteor's defaults. Only the properties you specify are overridden;
|
|
67
|
+
* everything else is preserved.
|
|
68
|
+
* @param swcConfig - SWC loader options to merge with defaults
|
|
61
69
|
* @returns A config object with SWC loader config
|
|
70
|
+
* @example ...Meteor.extendSwcConfig({ jsc: { parser: { decorators: true } } })
|
|
62
71
|
*/
|
|
63
72
|
extendSwcConfig: (swcConfig: SwcLoaderOptions) => Record<string, object>;
|
|
73
|
+
/**
|
|
74
|
+
* Replace the SWC loader config entirely, discarding Meteor's defaults.
|
|
75
|
+
* @param swcConfig - Complete SWC loader options (replaces defaults)
|
|
76
|
+
* @returns A config object with SWC loader config
|
|
77
|
+
* @example ...Meteor.replaceSwcConfig({ jsc: { target: 'es2020' } })
|
|
78
|
+
*/
|
|
79
|
+
replaceSwcConfig: (swcConfig: SwcLoaderOptions) => Record<string, object>;
|
|
64
80
|
/**
|
|
65
81
|
* Extend Rspack configs.
|
|
66
82
|
* @returns A config object with merged configs
|
|
83
|
+
* @example ...Meteor.extendConfig(configA, configB)
|
|
67
84
|
*/
|
|
68
85
|
extendConfig: (...configs: Record<string, object>[]) => Record<string, object>;
|
|
69
86
|
|
|
@@ -71,10 +88,43 @@ type MeteorEnv = Record<string, any> & {
|
|
|
71
88
|
* Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
|
|
72
89
|
* @param matchers - String, RegExp, function, or array of them to match plugin names
|
|
73
90
|
* @returns The modified config object
|
|
91
|
+
* @example ...Meteor.disablePlugins(['DefinePlugin', /HtmlRspack/])
|
|
74
92
|
*/
|
|
75
93
|
disablePlugins: (
|
|
76
94
|
matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array<string | RegExp | ((plugin: any, index: number) => boolean)>
|
|
77
95
|
) => Record<string, any>;
|
|
96
|
+
/**
|
|
97
|
+
* Omit `Meteor.isDevelopment` and `Meteor.isProduction` from the DefinePlugin so
|
|
98
|
+
* the bundle is not tied to a specific Meteor environment (portable builds).
|
|
99
|
+
* @returns A config fragment with `meteor.enablePortableBuild: true`
|
|
100
|
+
* @example ...Meteor.enablePortableBuild()
|
|
101
|
+
*/
|
|
102
|
+
enablePortableBuild: () => Record<string, any>;
|
|
103
|
+
/**
|
|
104
|
+
* Persist build-output files to disk during development.
|
|
105
|
+
* HTML files are always persisted automatically.
|
|
106
|
+
*
|
|
107
|
+
* Matchers: `string` (endsWith), `RegExp`, or `(filePath) => boolean`.
|
|
108
|
+
* Array form defaults to `always`. Object form supports `once` and `always`.
|
|
109
|
+
* - `always` — written on every build (default)
|
|
110
|
+
* - `once` — first build only (e.g. service workers)
|
|
111
|
+
*
|
|
112
|
+
* @param matchers - Array or `{ once?, always? }` of matchers
|
|
113
|
+
* @returns A config fragment with `devServer.devMiddleware.writeToDisk`
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ...Meteor.persistDevFiles({ once: ['sw.js'], always: ['manifest.json'] })
|
|
117
|
+
*/
|
|
118
|
+
persistDevFiles: (
|
|
119
|
+
matchers:
|
|
120
|
+
| (string | RegExp | ((filePath: string) => boolean))[]
|
|
121
|
+
| {
|
|
122
|
+
/** Files written on the first build only. */
|
|
123
|
+
once?: (string | RegExp | ((filePath: string) => boolean))[];
|
|
124
|
+
/** Files written on every build. */
|
|
125
|
+
always?: (string | RegExp | ((filePath: string) => boolean))[];
|
|
126
|
+
}
|
|
127
|
+
) => Record<string, object>;
|
|
78
128
|
}
|
|
79
129
|
|
|
80
130
|
export type ConfigFactory = (
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract local file dependencies from a config file by parsing require/import statements using AST
|
|
6
|
+
* @param {string} configFilePath - Path to the config file to parse
|
|
7
|
+
* @returns {string[]} - Array of absolute paths to local dependencies
|
|
8
|
+
*/
|
|
9
|
+
function extractLocalDependencies(configFilePath) {
|
|
10
|
+
if (!configFilePath || !fs.existsSync(configFilePath)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const swc = require('@swc/core');
|
|
16
|
+
const content = fs.readFileSync(configFilePath, 'utf-8');
|
|
17
|
+
const configDir = path.dirname(configFilePath);
|
|
18
|
+
const projectDir = process.cwd();
|
|
19
|
+
const dependencies = [];
|
|
20
|
+
|
|
21
|
+
// Parse the file into an AST
|
|
22
|
+
const ast = swc.parseSync(content, {
|
|
23
|
+
syntax: 'ecmascript',
|
|
24
|
+
dynamicImport: true,
|
|
25
|
+
target: 'es2020',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Visit all nodes to find import/require statements
|
|
29
|
+
visitNode(ast, (node) => {
|
|
30
|
+
let modulePath = null;
|
|
31
|
+
|
|
32
|
+
// Handle require() calls: require('./plugin')
|
|
33
|
+
if (node.type === 'CallExpression' &&
|
|
34
|
+
node.callee.type === 'Identifier' &&
|
|
35
|
+
node.callee.value === 'require' &&
|
|
36
|
+
node.arguments.length > 0) {
|
|
37
|
+
const arg = node.arguments[0];
|
|
38
|
+
if (arg.expression?.type === 'StringLiteral') {
|
|
39
|
+
modulePath = arg.expression.value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle dynamic import() calls: import('./plugin')
|
|
44
|
+
if (node.type === 'CallExpression' &&
|
|
45
|
+
node.callee.type === 'Import' &&
|
|
46
|
+
node.arguments.length > 0) {
|
|
47
|
+
const arg = node.arguments[0];
|
|
48
|
+
if (arg.expression?.type === 'StringLiteral') {
|
|
49
|
+
modulePath = arg.expression.value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle static imports: import x from './plugin'
|
|
54
|
+
if (node.type === 'ImportDeclaration' && node.source?.type === 'StringLiteral') {
|
|
55
|
+
modulePath = node.source.value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle export re-exports: export * from './plugin'
|
|
59
|
+
if (node.type === 'ExportAllDeclaration' && node.source?.type === 'StringLiteral') {
|
|
60
|
+
modulePath = node.source.value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle named export re-exports: export { x } from './plugin'
|
|
64
|
+
if (node.type === 'ExportNamedDeclaration' && node.source?.type === 'StringLiteral') {
|
|
65
|
+
modulePath = node.source.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// If we found a module path, try to resolve it
|
|
69
|
+
if (modulePath) {
|
|
70
|
+
const resolvedPath = resolveLocalModule(modulePath, configDir, projectDir);
|
|
71
|
+
if (resolvedPath) {
|
|
72
|
+
dependencies.push(resolvedPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Remove duplicates
|
|
78
|
+
return [...new Set(dependencies)];
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.warn('[Rspack Cache] Failed to parse config dependencies:', error.message);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Recursively visit all nodes in an AST
|
|
87
|
+
* @param {Object} node - AST node
|
|
88
|
+
* @param {Function} callback - Function to call for each node
|
|
89
|
+
*/
|
|
90
|
+
function visitNode(node, callback) {
|
|
91
|
+
if (!node || typeof node !== 'object') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
callback(node);
|
|
96
|
+
|
|
97
|
+
// Visit all properties of the node
|
|
98
|
+
for (const key in node) {
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(node, key)) {
|
|
100
|
+
const value = node[key];
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
value.forEach(child => visitNode(child, callback));
|
|
103
|
+
} else if (typeof value === 'object') {
|
|
104
|
+
visitNode(value, callback);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve a module path to an absolute path if it's a local file
|
|
112
|
+
* @param {string} modulePath - Module path from require/import statement
|
|
113
|
+
* @param {string} configDir - Directory containing the config file
|
|
114
|
+
* @param {string} projectDir - Project root directory
|
|
115
|
+
* @returns {string|null} - Resolved absolute path or null
|
|
116
|
+
*/
|
|
117
|
+
function resolveLocalModule(modulePath, configDir, projectDir) {
|
|
118
|
+
// Only process relative paths (starts with . or ..)
|
|
119
|
+
if (!modulePath.startsWith('.')) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
let resolvedPath = path.resolve(configDir, modulePath);
|
|
125
|
+
const extensions = ['.js', '.mjs', '.cjs', '.ts', '.json'];
|
|
126
|
+
|
|
127
|
+
// If the path exists as-is, check if it's a directory needing index resolution
|
|
128
|
+
if (fs.existsSync(resolvedPath)) {
|
|
129
|
+
if (fs.statSync(resolvedPath).isDirectory()) {
|
|
130
|
+
let found = false;
|
|
131
|
+
for (const ext of extensions) {
|
|
132
|
+
const indexPath = path.join(resolvedPath, `index${ext}`);
|
|
133
|
+
if (fs.existsSync(indexPath)) {
|
|
134
|
+
resolvedPath = indexPath;
|
|
135
|
+
found = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!found) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Try common extensions if file doesn't exist as-is
|
|
145
|
+
let found = false;
|
|
146
|
+
|
|
147
|
+
for (const ext of extensions) {
|
|
148
|
+
const pathWithExt = resolvedPath + ext;
|
|
149
|
+
if (fs.existsSync(pathWithExt)) {
|
|
150
|
+
resolvedPath = pathWithExt;
|
|
151
|
+
found = true;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If still not found, return null
|
|
157
|
+
if (!found) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Verify file is within project (not node_modules)
|
|
163
|
+
const resolvedReal = fs.realpathSync(resolvedPath);
|
|
164
|
+
const projectReal = fs.realpathSync(projectDir);
|
|
165
|
+
|
|
166
|
+
const isWithinProject =
|
|
167
|
+
resolvedReal === projectReal ||
|
|
168
|
+
resolvedReal.startsWith(projectReal + path.sep);
|
|
169
|
+
const hasNodeModulesSegment = resolvedReal.split(path.sep).includes('node_modules');
|
|
170
|
+
|
|
171
|
+
if (isWithinProject && !hasNodeModulesSegment) {
|
|
172
|
+
return resolvedPath;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Silently ignore resolution errors
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
extractLocalDependencies,
|
|
183
|
+
resolveLocalModule,
|
|
184
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { cleanOmittedPaths } = require("./mergeRulesSplitOverlap.js");
|
|
4
|
+
const { mergeMeteorRspackFragments } = require("./meteorRspackConfigFactory.js");
|
|
5
|
+
|
|
6
|
+
// Helper function to load and process config files
|
|
7
|
+
async function loadAndProcessConfig(configPath, configType, Meteor, argv, disableWarnings) {
|
|
8
|
+
try {
|
|
9
|
+
// Load the config file
|
|
10
|
+
let config;
|
|
11
|
+
if (path.extname(configPath) === '.mjs') {
|
|
12
|
+
// For ESM modules, we need to use dynamic import
|
|
13
|
+
const fileUrl = `file://${configPath}`;
|
|
14
|
+
const module = await import(fileUrl);
|
|
15
|
+
config = module.default || module;
|
|
16
|
+
} else {
|
|
17
|
+
// For CommonJS modules, we can use require
|
|
18
|
+
config = require(configPath)?.default || require(configPath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Process the config
|
|
22
|
+
const rawConfig = typeof config === 'function' ? config(Meteor, argv) : config;
|
|
23
|
+
const resolvedConfig = await Promise.resolve(rawConfig);
|
|
24
|
+
const userConfig = resolvedConfig && '0' in resolvedConfig ? resolvedConfig[0] : resolvedConfig;
|
|
25
|
+
|
|
26
|
+
// Define omitted paths and warning function
|
|
27
|
+
const omitPaths = [
|
|
28
|
+
"name",
|
|
29
|
+
"target",
|
|
30
|
+
"entry",
|
|
31
|
+
"output.path",
|
|
32
|
+
"output.filename",
|
|
33
|
+
...(Meteor.isServer ? ["optimization.splitChunks", "optimization.runtimeChunk"] : []),
|
|
34
|
+
].filter(Boolean);
|
|
35
|
+
|
|
36
|
+
const warningFn = path => {
|
|
37
|
+
if (disableWarnings) return;
|
|
38
|
+
console.warn(
|
|
39
|
+
`[${configType}] Ignored custom "${path}" — reserved for Meteor-Rspack integration.`,
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Clean omitted paths and merge Meteor Rspack fragments
|
|
44
|
+
let nextConfig = cleanOmittedPaths(userConfig, {
|
|
45
|
+
omitPaths,
|
|
46
|
+
warningFn,
|
|
47
|
+
});
|
|
48
|
+
nextConfig = mergeMeteorRspackFragments(nextConfig);
|
|
49
|
+
|
|
50
|
+
return nextConfig;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`Error loading ${configType} from ${configPath}:`, error);
|
|
53
|
+
if (configType === 'rspack.config.js') {
|
|
54
|
+
throw error; // Only rethrow for project config
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Loads both the user's Rspack configuration and its potential override.
|
|
62
|
+
*
|
|
63
|
+
* @param {string|undefined} projectConfigPath
|
|
64
|
+
* @param {object} Meteor
|
|
65
|
+
* @param {object} argv
|
|
66
|
+
* @returns {Promise<{ nextUserConfig: object|null, nextOverrideConfig: object|null }>}
|
|
67
|
+
*/
|
|
68
|
+
async function loadUserAndOverrideConfig(projectConfigPath, Meteor, argv) {
|
|
69
|
+
let nextUserConfig = null;
|
|
70
|
+
let nextOverrideConfig = null;
|
|
71
|
+
|
|
72
|
+
const projectDir = process.cwd();
|
|
73
|
+
const isMeteorPackageConfig = projectDir.includes("/packages/rspack");
|
|
74
|
+
|
|
75
|
+
if (projectConfigPath) {
|
|
76
|
+
const configDir = path.dirname(projectConfigPath);
|
|
77
|
+
const configFileName = path.basename(projectConfigPath);
|
|
78
|
+
const configExt = path.extname(configFileName);
|
|
79
|
+
const configNameWithoutExt = configFileName.replace(configExt, '');
|
|
80
|
+
const configNameFull = `${configNameWithoutExt}.override${configExt}`;
|
|
81
|
+
const overrideConfigPath = path.join(configDir, configNameFull);
|
|
82
|
+
|
|
83
|
+
if (fs.existsSync(overrideConfigPath)) {
|
|
84
|
+
nextOverrideConfig = await loadAndProcessConfig(
|
|
85
|
+
overrideConfigPath,
|
|
86
|
+
configNameFull,
|
|
87
|
+
Meteor,
|
|
88
|
+
argv,
|
|
89
|
+
Meteor.isAngularEnabled
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
|
|
94
|
+
// Check if there's a .mjs or .cjs version of the config file
|
|
95
|
+
const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
|
|
96
|
+
const cjsConfigPath = projectConfigPath.replace(/\.js$/, '.cjs');
|
|
97
|
+
|
|
98
|
+
let projectConfigPathToUse = projectConfigPath;
|
|
99
|
+
if (fs.existsSync(mjsConfigPath)) {
|
|
100
|
+
projectConfigPathToUse = mjsConfigPath;
|
|
101
|
+
} else if (fs.existsSync(cjsConfigPath)) {
|
|
102
|
+
projectConfigPathToUse = cjsConfigPath;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
nextUserConfig = await loadAndProcessConfig(
|
|
106
|
+
projectConfigPathToUse,
|
|
107
|
+
'rspack.config.js',
|
|
108
|
+
Meteor,
|
|
109
|
+
argv,
|
|
110
|
+
Meteor.isAngularEnabled
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { nextUserConfig, nextOverrideConfig };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
loadAndProcessConfig,
|
|
120
|
+
loadUserAndOverrideConfig,
|
|
121
|
+
};
|