mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge pull request #13761 from meteor/swc-dynamic-config
Support swc.config.js file for dynamic config
This commit is contained in:
@@ -5,6 +5,8 @@ const reifyCompile = Npm.require("@meteorjs/reify/lib/compiler").compile;
|
||||
const reifyAcornParse = Npm.require("@meteorjs/reify/lib/parsers/acorn").parse;
|
||||
var fs = Npm.require('fs');
|
||||
var path = Npm.require('path');
|
||||
var vm = Npm.require('vm');
|
||||
var crypto = Npm.require('crypto');
|
||||
|
||||
/**
|
||||
* A compiler that can be instantiated with features and used inside
|
||||
@@ -145,15 +147,37 @@ BCp.initializeMeteorAppConfig = function () {
|
||||
let lastModifiedSwcConfig;
|
||||
let lastModifiedSwcConfigTime;
|
||||
BCp.initializeMeteorAppSwcrc = function () {
|
||||
if (!lastModifiedSwcConfig && !fs.existsSync(`${getMeteorAppDir()}/.swcrc`)) {
|
||||
const hasSwcRc = fs.existsSync(`${getMeteorAppDir()}/.swcrc`);
|
||||
const hasSwcJs = !hasSwcRc && fs.existsSync(`${getMeteorAppDir()}/swc.config.js`);
|
||||
if (!lastModifiedSwcConfig && !hasSwcRc && !hasSwcJs) {
|
||||
return;
|
||||
}
|
||||
const currentLastModifiedConfigTime = fs
|
||||
.statSync(`${getMeteorAppDir()}/.swcrc`)
|
||||
?.mtime?.getTime();
|
||||
const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc';
|
||||
const filePath = `${getMeteorAppDir()}/${swcFile}`;
|
||||
const fileStats = fs.statSync(filePath);
|
||||
const fileModTime = fileStats?.mtime?.getTime();
|
||||
|
||||
let currentLastModifiedConfigTime;
|
||||
if (hasSwcJs) {
|
||||
// For dynamic JS files, first get the resolved configuration
|
||||
const resolvedConfig = lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile);
|
||||
// Calculate a hash of the resolved configuration to detect changes
|
||||
const contentHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(JSON.stringify(resolvedConfig))
|
||||
.digest('hex');
|
||||
// Combine file modification time and content hash to create a unique identifier
|
||||
currentLastModifiedConfigTime = `${fileModTime}-${contentHash}`;
|
||||
// Store the resolved configuration
|
||||
lastModifiedSwcConfig = resolvedConfig;
|
||||
} else {
|
||||
// For static JSON files, just use the file modification time
|
||||
currentLastModifiedConfigTime = fileModTime;
|
||||
}
|
||||
|
||||
if (currentLastModifiedConfigTime !== lastModifiedSwcConfigTime) {
|
||||
lastModifiedSwcConfigTime = currentLastModifiedConfigTime;
|
||||
lastModifiedSwcConfig = getMeteorAppSwcrc();
|
||||
lastModifiedSwcConfig = getMeteorAppSwcrc(swcFile);
|
||||
|
||||
// Resolve custom baseUrl to an absolute path pointing to the project root
|
||||
if (lastModifiedSwcConfig.jsc && lastModifiedSwcConfig.jsc.baseUrl) {
|
||||
@@ -879,11 +903,34 @@ function getMeteorAppPackageJson() {
|
||||
);
|
||||
}
|
||||
|
||||
function getMeteorAppSwcrc() {
|
||||
function getMeteorAppSwcrc(file = '.swcrc') {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(`${getMeteorAppDir()}/.swcrc`, 'utf-8'));
|
||||
const filePath = `${getMeteorAppDir()}/${file}`;
|
||||
if (file.endsWith('.js')) {
|
||||
let content = fs.readFileSync(filePath, 'utf-8');
|
||||
// Check if the content uses ES module syntax (export default)
|
||||
if (content.includes('export default')) {
|
||||
// Transform ES module syntax to CommonJS
|
||||
content = content.replace(/export\s+default\s+/, 'module.exports = ');
|
||||
}
|
||||
const script = new vm.Script(`
|
||||
(function() {
|
||||
const module = {};
|
||||
module.exports = {};
|
||||
(function(exports, module) {
|
||||
${content}
|
||||
})(module.exports, module);
|
||||
return module.exports;
|
||||
})()
|
||||
`);
|
||||
const context = vm.createContext({ process });
|
||||
return script.runInContext(context);
|
||||
} else {
|
||||
// For .swcrc and other JSON files, parse as JSON
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing .swcrc file', e);
|
||||
console.error(`Error parsing ${file} file`, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ selftest.define("modern build stack", async function () {
|
||||
await s.cd("modern");
|
||||
|
||||
s.set("METEOR_PROFILE", "0");
|
||||
|
||||
|
||||
await writeModernConfig(s, true);
|
||||
|
||||
const run = s.run();
|
||||
@@ -297,6 +297,12 @@ async function writeSwcrcConfig(s, config) {
|
||||
s.write(".swcrc", JSON.stringify(json, null, 2) + "\n");
|
||||
}
|
||||
|
||||
async function writeSwcConfigJs(s, config) {
|
||||
// Create a JavaScript file that exports the configuration
|
||||
const jsContent = `module.exports = ${JSON.stringify(config, null, 2)};`;
|
||||
s.write("swc.config.js", jsContent);
|
||||
}
|
||||
|
||||
selftest.define("modern build stack - transpiler custom .swcrc", async function () {
|
||||
const currentMeteorModern = process.env.METEOR_MODERN;
|
||||
process.env.METEOR_MODERN = '';
|
||||
@@ -328,6 +334,50 @@ selftest.define("modern build stack - transpiler custom .swcrc", async function
|
||||
process.env.METEOR_MODERN = currentMeteorModern;
|
||||
});
|
||||
|
||||
selftest.define("modern build stack - transpiler custom swc.config.js", async function () {
|
||||
const currentMeteorModern = process.env.METEOR_MODERN;
|
||||
process.env.METEOR_MODERN = '';
|
||||
|
||||
const s = new Sandbox();
|
||||
await s.init();
|
||||
|
||||
await s.createApp("modern", "modern");
|
||||
await s.cd("modern");
|
||||
|
||||
// Remove the .swcrc file to ensure we're using swc.config.js
|
||||
s.unlink(".swcrc");
|
||||
|
||||
// Write the swc.config.js file with the same configuration
|
||||
await writeSwcConfigJs(s, {
|
||||
jsc: {
|
||||
baseUrl: "./",
|
||||
paths: {
|
||||
"@swcAlias/*": ["swcAlias/*"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await writeConfig(s, {
|
||||
modern: true,
|
||||
mainModule: {
|
||||
client: 'client/main.js',
|
||||
server: 'server/alias.js',
|
||||
},
|
||||
});
|
||||
|
||||
const run = s.run();
|
||||
|
||||
run.waitSecs(waitToStart);
|
||||
await run.match("App running at");
|
||||
|
||||
/* custom swc.config.js and alias resolution */
|
||||
await run.match(/alias resolved/, false, true);
|
||||
|
||||
await run.stop();
|
||||
|
||||
process.env.METEOR_MODERN = currentMeteorModern;
|
||||
});
|
||||
|
||||
selftest.define("modern build stack - transpiler files", async function () {
|
||||
process.env.METEOR_MODERN = 'true';
|
||||
const s = new Sandbox();
|
||||
@@ -442,7 +492,7 @@ selftest.define("modern build stack - test swc minifier", async function () {
|
||||
|
||||
await s.createApp(appName, "modern");
|
||||
await s.cd(appName);
|
||||
|
||||
|
||||
await writeConfig(s, {
|
||||
modern: true,
|
||||
mainModule: {
|
||||
@@ -452,7 +502,7 @@ selftest.define("modern build stack - test swc minifier", async function () {
|
||||
});
|
||||
|
||||
s.set("NODE_INSPECTOR_IPC", "1");
|
||||
|
||||
|
||||
await writeModernConfig(s, {
|
||||
minifier: true
|
||||
});
|
||||
|
||||
@@ -121,6 +121,10 @@ You can also configure other options using the `.swcrc` format. One common case
|
||||
|
||||
This overrides Meteor's internal SWC config to apply your settings, ensuring SWC processes `.js` or `.ts` files with React components without falling back to Babel.
|
||||
|
||||
Use `swc.config.js` in your project root for dynamic configuration. Meteor will import and apply the SWC config automatically. This lets you choose a config based on environment variables or other runtime factors.
|
||||
|
||||
Explore additional custom SWC configs, including ["Import Aliases"](#import-aliases) and ["React Runtime"](#react-runtime).
|
||||
|
||||
## Config API
|
||||
|
||||
- `modern.transpiler: [true|false]` - Default: `true`
|
||||
@@ -190,6 +194,27 @@ To use the same aliases in SWC, add them to your [.swcrc](#custom-swcrc):
|
||||
}
|
||||
```
|
||||
|
||||
You can use `swc.config.js` to define different aliases based on an environment variable.
|
||||
|
||||
``` js
|
||||
var mode = process.env.MODE_ENV;
|
||||
|
||||
var userAliases = {
|
||||
"@ui/*": ["user/*"],
|
||||
};
|
||||
|
||||
var adminAliases = {
|
||||
"@ui/*": ["admin/*"],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
jsc: {
|
||||
baseUrl: "./",
|
||||
paths: mode === "USER" ? userAliases : adminAliases,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
This enables you to use `@ui/components` instead of `./ui/components` in your imports.
|
||||
|
||||
### React Runtime
|
||||
|
||||
Reference in New Issue
Block a user