Skip to content

Commit 337c31c

Browse files
authored
First-class MDX support (#10064)
1 parent 4f3bcaa commit 337c31c

28 files changed

+1610
-1217
lines changed

Cargo.lock

+118-108
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ napi = ["dep:napi", "dep:napi-derive", "dep:crossbeam-channel"]
99

1010
[dependencies]
1111
indexmap = "1.9.2"
12-
swc_core = { version = "6.0.1", features = [
12+
swc_core = { version = "9", features = [
1313
"common",
1414
"common_ahash",
1515
"common_sourcemap",

crates/macros/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ pub enum JsValue {
377377
}
378378

379379
pub struct Evaluator<'a> {
380-
constants: HashMap<Id, Result<JsValue, Span>>,
380+
pub constants: HashMap<Id, Result<JsValue, Span>>,
381381
source_map: &'a SourceMap,
382382
}
383383

packages/configs/default/index.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"@parcel/transformer-worklet",
99
"..."
1010
],
11+
"*.mdx": [
12+
// For backward compatibility, include the old transformer
13+
// so it is used if already installed in the project.
14+
// Otherwise, the JS transformer will handle MDX.
15+
"@parcel/transformer-mdx",
16+
"@parcel/transformer-js"
17+
],
1118
"*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": [
1219
"@parcel/transformer-babel",
1320
"@parcel/transformer-js",
@@ -33,7 +40,6 @@
3340
"*.pug": ["@parcel/transformer-pug"],
3441
"*.coffee": ["@parcel/transformer-coffeescript"],
3542
"*.elm": ["@parcel/transformer-elm"],
36-
"*.mdx": ["@parcel/transformer-mdx"],
3743
"*.vue": ["@parcel/transformer-vue"],
3844
"template:*.vue": ["@parcel/transformer-vue"],
3945
"script:*.vue": ["@parcel/transformer-vue"],

packages/configs/default/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"@parcel/transformer-inline-string": "2.13.3",
6464
"@parcel/transformer-jsonld": "2.13.3",
6565
"@parcel/transformer-less": "2.13.3",
66-
"@parcel/transformer-mdx": "2.13.3",
6766
"@parcel/transformer-pug": "2.13.3",
6867
"@parcel/transformer-sass": "2.13.3",
6968
"@parcel/transformer-stylus": "2.13.3",
@@ -76,6 +75,9 @@
7675
"@parcel/transformer-xml": "2.13.3",
7776
"@parcel/transformer-yaml": "2.13.3"
7877
},
78+
"optionalParcelDependencies": [
79+
"@parcel/transformer-mdx"
80+
],
7981
"peerDependencies": {
8082
"@parcel/core": "^2.13.3"
8183
}

packages/configs/default/test/config.test.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import assert from 'assert';
44

5-
import config from '../';
65
import packageJson from '../package.json';
6+
import json5 from 'json5';
7+
import fs from 'fs';
78

89
describe('@parcel/config-default', () => {
910
let packageJsonDependencyNames: Set<string>;
@@ -13,7 +14,11 @@ describe('@parcel/config-default', () => {
1314
packageJsonDependencyNames = new Set([
1415
...Object.keys(packageJson.dependencies || {}),
1516
...Object.keys(packageJson.parcelDependencies || {}),
17+
...packageJson.optionalParcelDependencies,
1618
]);
19+
let config = json5.parse(
20+
fs.readFileSync(__dirname + '/../index.json', 'utf8'),
21+
);
1722
configPackageReferences = collectConfigPackageReferences(config);
1823
});
1924

packages/core/core/src/ParcelConfig.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import ThrowableDiagnostic, {
2828
generateJSONCodeHighlights,
2929
} from '@parcel/diagnostic';
3030
import json5 from 'json5';
31+
import nullthrows from 'nullthrows';
3132

3233
import {globToRegex} from '@parcel/utils';
3334
import {basename} from 'path';
@@ -121,7 +122,7 @@ export default class ParcelConfig {
121122
version: Semver,
122123
resolveFrom: ProjectPath,
123124
range: ?SemverRange,
124-
|}> {
125+
|} | null> {
125126
let plugin = this.pluginCache.get(node.packageName);
126127
if (plugin) {
127128
return plugin;
@@ -138,8 +139,11 @@ export default class ParcelConfig {
138139
return plugin;
139140
}
140141

141-
async loadPlugin<T>(node: ParcelPluginNode): Promise<LoadedPlugin<T>> {
142+
async loadPlugin<T>(node: ParcelPluginNode): Promise<LoadedPlugin<T> | null> {
142143
let plugin = await this._loadPlugin(node);
144+
if (!plugin) {
145+
return null;
146+
}
143147
return {
144148
...plugin,
145149
name: node.packageName,
@@ -151,10 +155,12 @@ export default class ParcelConfig {
151155
this.pluginCache.delete(packageName);
152156
}
153157

154-
loadPlugins<T>(
158+
async loadPlugins<T>(
155159
plugins: PureParcelConfigPipeline,
156160
): Promise<Array<LoadedPlugin<T>>> {
157-
return Promise.all(plugins.map(p => this.loadPlugin<T>(p)));
161+
return (await Promise.all(plugins.map(p => this.loadPlugin<T>(p)))).filter(
162+
Boolean,
163+
);
158164
}
159165

160166
async getResolvers(): Promise<Array<LoadedPlugin<Resolver<mixed>>>> {
@@ -228,7 +234,7 @@ export default class ParcelConfig {
228234
);
229235
}
230236

231-
return this.loadPlugin<Bundler<mixed>>(this.bundler);
237+
return nullthrows(await this.loadPlugin<Bundler<mixed>>(this.bundler));
232238
}
233239

234240
async getNamers(): Promise<Array<LoadedPlugin<Namer<mixed>>>> {
@@ -265,7 +271,7 @@ export default class ParcelConfig {
265271
'/packagers',
266272
);
267273
}
268-
return this.loadPlugin<Packager<mixed, mixed>>(packager);
274+
return nullthrows(await this.loadPlugin<Packager<mixed, mixed>>(packager));
269275
}
270276

271277
_getOptimizerNodes(

packages/core/core/src/Transformation.js

+5
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,11 @@ export default class Transformation {
569569
configKeyPath?: string,
570570
parcelConfig: ParcelConfig,
571571
): Promise<$ReadOnlyArray<TransformerResult | UncommittedAsset>> {
572+
if (asset.transformers.has(transformerName)) {
573+
return [asset];
574+
}
575+
asset.transformers.add(transformerName);
576+
572577
const logger = new PluginLogger({origin: transformerName});
573578
const tracer = new PluginTracer({
574579
origin: transformerName,

packages/core/core/src/UncommittedAsset.js

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export default class UncommittedAsset {
5555
idBase: ?string;
5656
invalidations: Invalidations;
5757
generate: ?() => Promise<GenerateOutput>;
58+
transformers: Set<string>;
5859

5960
constructor({
6061
value,
@@ -74,6 +75,7 @@ export default class UncommittedAsset {
7475
this.isASTDirty = isASTDirty || false;
7576
this.idBase = idBase;
7677
this.invalidations = invalidations || createInvalidations();
78+
this.transformers = new Set();
7779
}
7880

7981
/*

packages/core/core/src/assetUtils.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,16 @@ async function _generateFromAST(asset: CommittedAsset | UncommittedAsset) {
152152
}
153153

154154
let pluginName = nullthrows(asset.value.plugin);
155-
let {plugin} = await loadPlugin<Transformer<mixed>>(
156-
pluginName,
157-
fromProjectPath(
158-
asset.options.projectRoot,
159-
nullthrows(asset.value.configPath),
155+
let {plugin} = nullthrows(
156+
await loadPlugin<Transformer<mixed>>(
157+
pluginName,
158+
fromProjectPath(
159+
asset.options.projectRoot,
160+
nullthrows(asset.value.configPath),
161+
),
162+
nullthrows(asset.value.configKeyPath),
163+
asset.options,
160164
),
161-
nullthrows(asset.value.configKeyPath),
162-
asset.options,
163165
);
164166
let generate = plugin.generate?.bind(plugin);
165167
if (!generate) {

packages/core/core/src/loadParcelPlugin.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ export default async function loadPlugin<T>(
3131
version: Semver,
3232
resolveFrom: ProjectPath,
3333
range: ?SemverRange,
34-
|}> {
34+
|} | null> {
3535
let resolveFrom = configPath;
3636
let range;
37-
if (resolveFrom.includes(NODE_MODULES)) {
37+
let isOptional = false;
38+
if (
39+
resolveFrom.includes(NODE_MODULES) ||
40+
(process.env.PARCEL_BUILD_ENV !== 'production' &&
41+
/packages[/\\]configs/.test(resolveFrom))
42+
) {
3843
// Config packages can reference plugins, but cannot contain other plugins within them.
3944
// This forces every published plugin to be published separately so they can be mixed and matched if needed.
4045
if (pluginName.startsWith('.')) {
@@ -75,8 +80,11 @@ export default async function loadPlugin<T>(
7580
// If not in the config's dependencies, the plugin will be auto installed with
7681
// the version declared in "parcelDependencies".
7782
range = configPkg.config.parcelDependencies?.[pluginName];
83+
isOptional =
84+
Array.isArray(configPkg.config.optionalParcelDependencies) &&
85+
configPkg.config.optionalParcelDependencies.includes(pluginName);
7886

79-
if (range == null) {
87+
if (range == null && !isOptional) {
8088
let contents = await options.inputFS.readFile(
8189
configPkg.files[0].filePath,
8290
'utf8',
@@ -122,7 +130,7 @@ export default async function loadPlugin<T>(
122130
pluginName,
123131
resolveFrom,
124132
{
125-
shouldAutoInstall: options.shouldAutoInstall,
133+
shouldAutoInstall: options.shouldAutoInstall && !isOptional,
126134
range,
127135
},
128136
));
@@ -131,6 +139,10 @@ export default async function loadPlugin<T>(
131139
throw err;
132140
}
133141

142+
if (isOptional) {
143+
return null;
144+
}
145+
134146
let configContents = await options.inputFS.readFile(configPath, 'utf8');
135147
let alternatives = await findAlternativeNodeModules(
136148
options.inputFS,

packages/core/core/test/ParcelConfig.test.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {inputFS} from '@parcel/test-utils';
99
import {parseAndProcessConfig} from '../src/requests/ParcelConfigRequest';
1010
import {DEFAULT_OPTIONS} from './test-utils';
1111
import {toProjectPath} from '../src/projectPath';
12+
import nullthrows from 'nullthrows';
1213

1314
const PARCELRC_PATH = toProjectPath('/', '/.parcelrc');
1415

@@ -148,11 +149,13 @@ describe('ParcelConfig', () => {
148149
);
149150

150151
let warnStub = sinon.stub(logger, 'warn');
151-
let {plugin} = await config.loadPlugin({
152-
packageName: 'parcel-transformer-no-engines',
153-
resolveFrom: configFilePath,
154-
keyPath: '/transformers/*.js/0',
155-
});
152+
let {plugin} = nullthrows(
153+
await config.loadPlugin({
154+
packageName: 'parcel-transformer-no-engines',
155+
resolveFrom: configFilePath,
156+
keyPath: '/transformers/*.js/0',
157+
}),
158+
);
156159
assert(plugin);
157160
assert.equal(typeof plugin.transform, 'function');
158161
assert(warnStub.calledOnce);

packages/core/core/test/ParcelConfigRequest.test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
import {validatePackageName} from '../src/ParcelConfig.schema';
1717
import {DEFAULT_OPTIONS, relative} from './test-utils';
1818
import {toProjectPath} from '../src/projectPath';
19+
import json5 from 'json5';
20+
import fs from 'fs';
1921

2022
describe('ParcelConfigRequest', () => {
2123
describe('validatePackageName', () => {
@@ -696,7 +698,7 @@ describe('ParcelConfigRequest', () => {
696698
let defaultConfigPath = require.resolve('@parcel/config-default');
697699
let defaultConfig = await processConfig(
698700
{
699-
...require('@parcel/config-default'),
701+
...json5.parse(fs.readFileSync(defaultConfigPath, 'utf8')),
700702
filePath: defaultConfigPath,
701703
},
702704
DEFAULT_OPTIONS,
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"dependencies": {
3-
"react": "*"
3+
"react": "^19"
44
}
55
}

0 commit comments

Comments
 (0)