@lwc/babel-plugin-component 9.0.3 → 9.0.4-alpha.1
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/dist/constants.d.ts +4 -1
- package/dist/index.cjs +224 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +223 -2
- package/dist/private-method-transform.d.ts +18 -0
- package/dist/reverse-private-method-transform.d.ts +18 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils.d.ts +8 -1
- package/package.json +3 -3
package/dist/constants.d.ts
CHANGED
|
@@ -23,7 +23,10 @@ declare const TEMPLATE_KEY = "tmpl";
|
|
|
23
23
|
declare const COMPONENT_NAME_KEY = "sel";
|
|
24
24
|
declare const API_VERSION_KEY = "apiVersion";
|
|
25
25
|
declare const COMPONENT_CLASS_ID = "__lwc_component_class_internal";
|
|
26
|
+
declare const PRIVATE_METHOD_PREFIX = "__lwc_component_class_internal_private_";
|
|
27
|
+
declare const PRIVATE_METHOD_METADATA_KEY = "__lwcTransformedPrivateMethods";
|
|
28
|
+
declare const ENABLE_PRIVATE_METHODS_KEY = "enablePrivateMethods";
|
|
26
29
|
declare const SYNTHETIC_ELEMENT_INTERNALS_KEY = "enableSyntheticElementInternals";
|
|
27
30
|
declare const COMPONENT_FEATURE_FLAG_KEY = "componentFeatureFlag";
|
|
28
|
-
export { DECORATOR_TYPES, LWC_PACKAGE_ALIAS, LWC_PACKAGE_EXPORTS, LWC_COMPONENT_PROPERTIES, REGISTER_COMPONENT_ID, REGISTER_DECORATORS_ID, TEMPLATE_KEY, COMPONENT_NAME_KEY, API_VERSION_KEY, COMPONENT_CLASS_ID, SYNTHETIC_ELEMENT_INTERNALS_KEY, COMPONENT_FEATURE_FLAG_KEY, };
|
|
31
|
+
export { DECORATOR_TYPES, LWC_PACKAGE_ALIAS, LWC_PACKAGE_EXPORTS, LWC_COMPONENT_PROPERTIES, REGISTER_COMPONENT_ID, REGISTER_DECORATORS_ID, TEMPLATE_KEY, COMPONENT_NAME_KEY, API_VERSION_KEY, COMPONENT_CLASS_ID, PRIVATE_METHOD_PREFIX, PRIVATE_METHOD_METADATA_KEY, ENABLE_PRIVATE_METHODS_KEY, SYNTHETIC_ELEMENT_INTERNALS_KEY, COMPONENT_FEATURE_FLAG_KEY, };
|
|
29
32
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,9 @@ const TEMPLATE_KEY = 'tmpl';
|
|
|
41
41
|
const COMPONENT_NAME_KEY = 'sel';
|
|
42
42
|
const API_VERSION_KEY = 'apiVersion';
|
|
43
43
|
const COMPONENT_CLASS_ID = '__lwc_component_class_internal';
|
|
44
|
+
const PRIVATE_METHOD_PREFIX = '__lwc_component_class_internal_private_';
|
|
45
|
+
const PRIVATE_METHOD_METADATA_KEY = '__lwcTransformedPrivateMethods';
|
|
46
|
+
const ENABLE_PRIVATE_METHODS_KEY = 'enablePrivateMethods';
|
|
44
47
|
const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
|
|
45
48
|
const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
|
|
46
49
|
|
|
@@ -123,6 +126,9 @@ function component ({ types: t }) {
|
|
|
123
126
|
if (state.opts.enableSyntheticElementInternals === true) {
|
|
124
127
|
properties.push(t.objectProperty(t.identifier(SYNTHETIC_ELEMENT_INTERNALS_KEY), t.booleanLiteral(true)));
|
|
125
128
|
}
|
|
129
|
+
if (state.opts.enablePrivateMethods === true) {
|
|
130
|
+
properties.push(t.objectProperty(t.identifier(ENABLE_PRIVATE_METHODS_KEY), t.booleanLiteral(true)));
|
|
131
|
+
}
|
|
126
132
|
const registerComponentExpression = t.callExpression(registerComponentId, [
|
|
127
133
|
node,
|
|
128
134
|
t.objectExpression(properties),
|
|
@@ -259,6 +265,30 @@ function incrementMetricCounter(metric, state) {
|
|
|
259
265
|
function isErrorRecoveryMode(state) {
|
|
260
266
|
return state.file.opts?.parserOpts?.errorRecovery ?? false;
|
|
261
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
|
|
270
|
+
* These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
|
|
271
|
+
* so they must be transferred manually after node creation. Both the forward and reverse
|
|
272
|
+
* private-method transforms use this to maintain round-trip parity.
|
|
273
|
+
*/
|
|
274
|
+
function copyMethodMetadata(source, target) {
|
|
275
|
+
if (source.returnType != null)
|
|
276
|
+
target.returnType = source.returnType;
|
|
277
|
+
if (source.typeParameters != null)
|
|
278
|
+
target.typeParameters = source.typeParameters;
|
|
279
|
+
if (source.loc != null)
|
|
280
|
+
target.loc = source.loc;
|
|
281
|
+
if (source.abstract != null)
|
|
282
|
+
target.abstract = source.abstract;
|
|
283
|
+
if (source.access != null)
|
|
284
|
+
target.access = source.access;
|
|
285
|
+
if (source.accessibility != null)
|
|
286
|
+
target.accessibility = source.accessibility;
|
|
287
|
+
if (source.optional != null)
|
|
288
|
+
target.optional = source.optional;
|
|
289
|
+
if (source.override != null)
|
|
290
|
+
target.override = source.override;
|
|
291
|
+
}
|
|
262
292
|
|
|
263
293
|
/*
|
|
264
294
|
* Copyright (c) 2023, salesforce.com, inc.
|
|
@@ -1244,6 +1274,197 @@ function compilerVersionNumber({ types: t }) {
|
|
|
1244
1274
|
};
|
|
1245
1275
|
}
|
|
1246
1276
|
|
|
1277
|
+
/*
|
|
1278
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
1279
|
+
* All rights reserved.
|
|
1280
|
+
* SPDX-License-Identifier: MIT
|
|
1281
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
1282
|
+
*/
|
|
1283
|
+
// We only transform kind: 'method'. Other kinds ('get', 'set', 'constructor') are left alone.
|
|
1284
|
+
const METHOD_KIND = 'method';
|
|
1285
|
+
/**
|
|
1286
|
+
* Standalone Babel plugin that transforms private method identifiers from
|
|
1287
|
+
* `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
|
|
1288
|
+
*
|
|
1289
|
+
* This must be registered BEFORE the main LWC class transform plugin so that
|
|
1290
|
+
* private methods are converted to regular methods before decorator and class
|
|
1291
|
+
* property processing.
|
|
1292
|
+
*
|
|
1293
|
+
* Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
|
|
1294
|
+
* because the reverse transform has a ClassMethod visitor in the same Babel pass.
|
|
1295
|
+
* A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
|
|
1296
|
+
* immediately converts back, creating an infinite loop. The manual traverse ensures
|
|
1297
|
+
* all forward replacements complete before the reverse visitor sees any ClassMethod.
|
|
1298
|
+
*/
|
|
1299
|
+
function privateMethodTransform({ types: t, }) {
|
|
1300
|
+
return {
|
|
1301
|
+
visitor: {
|
|
1302
|
+
Program(path, state) {
|
|
1303
|
+
const transformedNames = new Set();
|
|
1304
|
+
// Phase 1: Collect base names of all private methods (kind: 'method')
|
|
1305
|
+
// so that Phase 2 can transform invocations even for forward references
|
|
1306
|
+
// (call site visited before the method definition).
|
|
1307
|
+
const privateMethodBaseNames = new Set();
|
|
1308
|
+
path.traverse({
|
|
1309
|
+
ClassPrivateMethod(methodPath) {
|
|
1310
|
+
const key = methodPath.get('key');
|
|
1311
|
+
if (key.isPrivateName() && methodPath.node.kind === METHOD_KIND) {
|
|
1312
|
+
privateMethodBaseNames.add(key.node.id.name);
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
});
|
|
1316
|
+
// Phase 2: Transform definitions and invocations
|
|
1317
|
+
path.traverse({
|
|
1318
|
+
ClassPrivateMethod(methodPath, methodState) {
|
|
1319
|
+
const key = methodPath.get('key');
|
|
1320
|
+
if (!key.isPrivateName()) {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (methodPath.node.kind !== METHOD_KIND) {
|
|
1324
|
+
handleError(methodPath, {
|
|
1325
|
+
errorInfo: errors.DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
|
|
1326
|
+
messageArgs: ['accessor methods'],
|
|
1327
|
+
}, methodState);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
const node = methodPath.node;
|
|
1331
|
+
// Reject private methods with decorators (e.g. @api, @track, @wire)
|
|
1332
|
+
if (node.decorators && node.decorators.length > 0) {
|
|
1333
|
+
handleError(methodPath, {
|
|
1334
|
+
errorInfo: errors.DecoratorErrors.DECORATOR_ON_PRIVATE_METHOD,
|
|
1335
|
+
}, methodState);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const privateName = key.node.id.name;
|
|
1339
|
+
const transformedName = `${PRIVATE_METHOD_PREFIX}${privateName}`;
|
|
1340
|
+
const keyReplacement = t.identifier(transformedName);
|
|
1341
|
+
// Create a new ClassMethod node to replace the ClassPrivateMethod
|
|
1342
|
+
// https://babeljs.io/docs/babel-types#classmethod
|
|
1343
|
+
const classMethod = t.classMethod(METHOD_KIND, keyReplacement, node.params, node.body, node.computed, node.static, node.generator, node.async);
|
|
1344
|
+
copyMethodMetadata(node, classMethod);
|
|
1345
|
+
// Replace the entire ClassPrivateMethod node with the new ClassMethod node
|
|
1346
|
+
// (we can't just replace the key of type PrivateName with type Identifier)
|
|
1347
|
+
methodPath.replaceWith(classMethod);
|
|
1348
|
+
transformedNames.add(transformedName);
|
|
1349
|
+
},
|
|
1350
|
+
MemberExpression(memberPath) {
|
|
1351
|
+
const property = memberPath.node.property;
|
|
1352
|
+
if (t.isPrivateName(property)) {
|
|
1353
|
+
const baseName = property.id.name;
|
|
1354
|
+
if (privateMethodBaseNames.has(baseName)) {
|
|
1355
|
+
const prefixedName = `${PRIVATE_METHOD_PREFIX}${baseName}`;
|
|
1356
|
+
memberPath
|
|
1357
|
+
.get('property')
|
|
1358
|
+
.replaceWith(t.identifier(prefixedName));
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
ClassPrivateProperty(propPath, propState) {
|
|
1363
|
+
handleError(propPath, {
|
|
1364
|
+
errorInfo: errors.DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
|
|
1365
|
+
messageArgs: ['fields'],
|
|
1366
|
+
}, propState);
|
|
1367
|
+
},
|
|
1368
|
+
}, state);
|
|
1369
|
+
state.file.metadata[PRIVATE_METHOD_METADATA_KEY] = transformedNames;
|
|
1370
|
+
},
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/*
|
|
1376
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
1377
|
+
* All rights reserved.
|
|
1378
|
+
* SPDX-License-Identifier: MIT
|
|
1379
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
1380
|
+
*/
|
|
1381
|
+
/**
|
|
1382
|
+
* Standalone Babel plugin that reverses the private method transformation by converting
|
|
1383
|
+
* methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
|
|
1384
|
+
* and restoring prefixed MemberExpression properties back to PrivateName nodes.
|
|
1385
|
+
*
|
|
1386
|
+
* This must be registered AFTER @babel/plugin-transform-class-properties so that
|
|
1387
|
+
* class properties are fully transformed before private methods are restored.
|
|
1388
|
+
*
|
|
1389
|
+
* Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
|
|
1390
|
+
* properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
|
|
1391
|
+
* abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
|
|
1392
|
+
*
|
|
1393
|
+
* @see {@link ./private-method-transform.ts} for original transformation
|
|
1394
|
+
*/
|
|
1395
|
+
function reversePrivateMethodTransform({ types: t, }) {
|
|
1396
|
+
// Scoped to this plugin instance's closure. Safe as long as each Babel run creates a
|
|
1397
|
+
// fresh plugin via LwcReversePrivateMethodTransform(); would accumulate across files if
|
|
1398
|
+
// the same instance were ever reused.
|
|
1399
|
+
const reverseTransformedNames = new Set();
|
|
1400
|
+
return {
|
|
1401
|
+
visitor: {
|
|
1402
|
+
ClassMethod(path, state) {
|
|
1403
|
+
const key = path.get('key');
|
|
1404
|
+
// kind: 'method' | 'get' | 'set' - only 'method' is in scope.
|
|
1405
|
+
if (key.isIdentifier() && path.node.kind === 'method') {
|
|
1406
|
+
const methodName = key.node.name;
|
|
1407
|
+
if (methodName.startsWith(PRIVATE_METHOD_PREFIX)) {
|
|
1408
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1409
|
+
// If the method was not transformed by the forward pass, it is a
|
|
1410
|
+
// user-defined method that collides with the reserved prefix.
|
|
1411
|
+
if (!forwardTransformedNames || !forwardTransformedNames.has(methodName)) {
|
|
1412
|
+
const message = errors.DecoratorErrors.PRIVATE_METHOD_NAME_COLLISION.message.replace('{0}', methodName);
|
|
1413
|
+
throw path.buildCodeFrameError(message);
|
|
1414
|
+
}
|
|
1415
|
+
const originalPrivateName = methodName.replace(PRIVATE_METHOD_PREFIX, '');
|
|
1416
|
+
const node = path.node;
|
|
1417
|
+
const classPrivateMethod = t.classPrivateMethod('method', t.privateName(t.identifier(originalPrivateName)), node.params, node.body, node.static);
|
|
1418
|
+
// Properties the t.classPrivateMethod() builder doesn't accept
|
|
1419
|
+
classPrivateMethod.async = node.async;
|
|
1420
|
+
classPrivateMethod.generator = node.generator;
|
|
1421
|
+
classPrivateMethod.computed = node.computed;
|
|
1422
|
+
copyMethodMetadata(node, classPrivateMethod);
|
|
1423
|
+
path.replaceWith(classPrivateMethod);
|
|
1424
|
+
reverseTransformedNames.add(methodName);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
},
|
|
1428
|
+
MemberExpression(path, state) {
|
|
1429
|
+
const property = path.node.property;
|
|
1430
|
+
if (!t.isIdentifier(property) || !property.name.startsWith(PRIVATE_METHOD_PREFIX)) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1434
|
+
if (!forwardTransformedNames || !forwardTransformedNames.has(property.name)) {
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const originalName = property.name.replace(PRIVATE_METHOD_PREFIX, '');
|
|
1438
|
+
path.get('property').replaceWith(t.privateName(t.identifier(originalName)));
|
|
1439
|
+
},
|
|
1440
|
+
// After all nodes have been visited, verify that every method the forward transform
|
|
1441
|
+
// renamed was also restored by the reverse transform. A mismatch here means an
|
|
1442
|
+
// intermediate plugin (e.g. @babel/plugin-transform-class-properties) removed or
|
|
1443
|
+
// renamed a prefixed method, leaving a mangled name in the final output.
|
|
1444
|
+
Program: {
|
|
1445
|
+
exit(_path, state) {
|
|
1446
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1447
|
+
if (!forwardTransformedNames) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const missingFromReverse = [];
|
|
1451
|
+
for (const name of forwardTransformedNames) {
|
|
1452
|
+
if (!reverseTransformedNames.has(name)) {
|
|
1453
|
+
missingFromReverse.push(name);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
if (missingFromReverse.length > 0) {
|
|
1457
|
+
throw new Error(`Private method transform count mismatch: ` +
|
|
1458
|
+
`forward transformed ${forwardTransformedNames.size} method(s), ` +
|
|
1459
|
+
`but reverse transformed ${reverseTransformedNames.size}. ` +
|
|
1460
|
+
`Missing reverse transforms for: ${missingFromReverse.join(', ')}`);
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1247
1468
|
/*
|
|
1248
1469
|
* Copyright (c) 2023, salesforce.com, inc.
|
|
1249
1470
|
* All rights reserved.
|
|
@@ -1292,6 +1513,8 @@ function LwcClassTransform(api) {
|
|
|
1292
1513
|
};
|
|
1293
1514
|
}
|
|
1294
1515
|
|
|
1516
|
+
exports.LwcPrivateMethodTransform = privateMethodTransform;
|
|
1517
|
+
exports.LwcReversePrivateMethodTransform = reversePrivateMethodTransform;
|
|
1295
1518
|
exports.default = LwcClassTransform;
|
|
1296
|
-
/** version: 9.0.
|
|
1519
|
+
/** version: 9.0.4-alpha.1 */
|
|
1297
1520
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { BabelAPI, LwcBabelPluginPass } from './types';
|
|
2
2
|
import type { PluginObj } from '@babel/core';
|
|
3
3
|
export type { LwcBabelPluginOptions } from './types';
|
|
4
|
+
export { default as LwcPrivateMethodTransform } from './private-method-transform';
|
|
5
|
+
export { default as LwcReversePrivateMethodTransform } from './reverse-private-method-transform';
|
|
4
6
|
/**
|
|
5
7
|
* The transform is done in 2 passes:
|
|
6
8
|
* - First, apply in a single AST traversal the decorators and the component transformation.
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,9 @@ const TEMPLATE_KEY = 'tmpl';
|
|
|
37
37
|
const COMPONENT_NAME_KEY = 'sel';
|
|
38
38
|
const API_VERSION_KEY = 'apiVersion';
|
|
39
39
|
const COMPONENT_CLASS_ID = '__lwc_component_class_internal';
|
|
40
|
+
const PRIVATE_METHOD_PREFIX = '__lwc_component_class_internal_private_';
|
|
41
|
+
const PRIVATE_METHOD_METADATA_KEY = '__lwcTransformedPrivateMethods';
|
|
42
|
+
const ENABLE_PRIVATE_METHODS_KEY = 'enablePrivateMethods';
|
|
40
43
|
const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
|
|
41
44
|
const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
|
|
42
45
|
|
|
@@ -119,6 +122,9 @@ function component ({ types: t }) {
|
|
|
119
122
|
if (state.opts.enableSyntheticElementInternals === true) {
|
|
120
123
|
properties.push(t.objectProperty(t.identifier(SYNTHETIC_ELEMENT_INTERNALS_KEY), t.booleanLiteral(true)));
|
|
121
124
|
}
|
|
125
|
+
if (state.opts.enablePrivateMethods === true) {
|
|
126
|
+
properties.push(t.objectProperty(t.identifier(ENABLE_PRIVATE_METHODS_KEY), t.booleanLiteral(true)));
|
|
127
|
+
}
|
|
122
128
|
const registerComponentExpression = t.callExpression(registerComponentId, [
|
|
123
129
|
node,
|
|
124
130
|
t.objectExpression(properties),
|
|
@@ -255,6 +261,30 @@ function incrementMetricCounter(metric, state) {
|
|
|
255
261
|
function isErrorRecoveryMode(state) {
|
|
256
262
|
return state.file.opts?.parserOpts?.errorRecovery ?? false;
|
|
257
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
|
|
266
|
+
* These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
|
|
267
|
+
* so they must be transferred manually after node creation. Both the forward and reverse
|
|
268
|
+
* private-method transforms use this to maintain round-trip parity.
|
|
269
|
+
*/
|
|
270
|
+
function copyMethodMetadata(source, target) {
|
|
271
|
+
if (source.returnType != null)
|
|
272
|
+
target.returnType = source.returnType;
|
|
273
|
+
if (source.typeParameters != null)
|
|
274
|
+
target.typeParameters = source.typeParameters;
|
|
275
|
+
if (source.loc != null)
|
|
276
|
+
target.loc = source.loc;
|
|
277
|
+
if (source.abstract != null)
|
|
278
|
+
target.abstract = source.abstract;
|
|
279
|
+
if (source.access != null)
|
|
280
|
+
target.access = source.access;
|
|
281
|
+
if (source.accessibility != null)
|
|
282
|
+
target.accessibility = source.accessibility;
|
|
283
|
+
if (source.optional != null)
|
|
284
|
+
target.optional = source.optional;
|
|
285
|
+
if (source.override != null)
|
|
286
|
+
target.override = source.override;
|
|
287
|
+
}
|
|
258
288
|
|
|
259
289
|
/*
|
|
260
290
|
* Copyright (c) 2023, salesforce.com, inc.
|
|
@@ -1240,6 +1270,197 @@ function compilerVersionNumber({ types: t }) {
|
|
|
1240
1270
|
};
|
|
1241
1271
|
}
|
|
1242
1272
|
|
|
1273
|
+
/*
|
|
1274
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
1275
|
+
* All rights reserved.
|
|
1276
|
+
* SPDX-License-Identifier: MIT
|
|
1277
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
1278
|
+
*/
|
|
1279
|
+
// We only transform kind: 'method'. Other kinds ('get', 'set', 'constructor') are left alone.
|
|
1280
|
+
const METHOD_KIND = 'method';
|
|
1281
|
+
/**
|
|
1282
|
+
* Standalone Babel plugin that transforms private method identifiers from
|
|
1283
|
+
* `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
|
|
1284
|
+
*
|
|
1285
|
+
* This must be registered BEFORE the main LWC class transform plugin so that
|
|
1286
|
+
* private methods are converted to regular methods before decorator and class
|
|
1287
|
+
* property processing.
|
|
1288
|
+
*
|
|
1289
|
+
* Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
|
|
1290
|
+
* because the reverse transform has a ClassMethod visitor in the same Babel pass.
|
|
1291
|
+
* A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
|
|
1292
|
+
* immediately converts back, creating an infinite loop. The manual traverse ensures
|
|
1293
|
+
* all forward replacements complete before the reverse visitor sees any ClassMethod.
|
|
1294
|
+
*/
|
|
1295
|
+
function privateMethodTransform({ types: t, }) {
|
|
1296
|
+
return {
|
|
1297
|
+
visitor: {
|
|
1298
|
+
Program(path, state) {
|
|
1299
|
+
const transformedNames = new Set();
|
|
1300
|
+
// Phase 1: Collect base names of all private methods (kind: 'method')
|
|
1301
|
+
// so that Phase 2 can transform invocations even for forward references
|
|
1302
|
+
// (call site visited before the method definition).
|
|
1303
|
+
const privateMethodBaseNames = new Set();
|
|
1304
|
+
path.traverse({
|
|
1305
|
+
ClassPrivateMethod(methodPath) {
|
|
1306
|
+
const key = methodPath.get('key');
|
|
1307
|
+
if (key.isPrivateName() && methodPath.node.kind === METHOD_KIND) {
|
|
1308
|
+
privateMethodBaseNames.add(key.node.id.name);
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
});
|
|
1312
|
+
// Phase 2: Transform definitions and invocations
|
|
1313
|
+
path.traverse({
|
|
1314
|
+
ClassPrivateMethod(methodPath, methodState) {
|
|
1315
|
+
const key = methodPath.get('key');
|
|
1316
|
+
if (!key.isPrivateName()) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (methodPath.node.kind !== METHOD_KIND) {
|
|
1320
|
+
handleError(methodPath, {
|
|
1321
|
+
errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
|
|
1322
|
+
messageArgs: ['accessor methods'],
|
|
1323
|
+
}, methodState);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const node = methodPath.node;
|
|
1327
|
+
// Reject private methods with decorators (e.g. @api, @track, @wire)
|
|
1328
|
+
if (node.decorators && node.decorators.length > 0) {
|
|
1329
|
+
handleError(methodPath, {
|
|
1330
|
+
errorInfo: DecoratorErrors.DECORATOR_ON_PRIVATE_METHOD,
|
|
1331
|
+
}, methodState);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
const privateName = key.node.id.name;
|
|
1335
|
+
const transformedName = `${PRIVATE_METHOD_PREFIX}${privateName}`;
|
|
1336
|
+
const keyReplacement = t.identifier(transformedName);
|
|
1337
|
+
// Create a new ClassMethod node to replace the ClassPrivateMethod
|
|
1338
|
+
// https://babeljs.io/docs/babel-types#classmethod
|
|
1339
|
+
const classMethod = t.classMethod(METHOD_KIND, keyReplacement, node.params, node.body, node.computed, node.static, node.generator, node.async);
|
|
1340
|
+
copyMethodMetadata(node, classMethod);
|
|
1341
|
+
// Replace the entire ClassPrivateMethod node with the new ClassMethod node
|
|
1342
|
+
// (we can't just replace the key of type PrivateName with type Identifier)
|
|
1343
|
+
methodPath.replaceWith(classMethod);
|
|
1344
|
+
transformedNames.add(transformedName);
|
|
1345
|
+
},
|
|
1346
|
+
MemberExpression(memberPath) {
|
|
1347
|
+
const property = memberPath.node.property;
|
|
1348
|
+
if (t.isPrivateName(property)) {
|
|
1349
|
+
const baseName = property.id.name;
|
|
1350
|
+
if (privateMethodBaseNames.has(baseName)) {
|
|
1351
|
+
const prefixedName = `${PRIVATE_METHOD_PREFIX}${baseName}`;
|
|
1352
|
+
memberPath
|
|
1353
|
+
.get('property')
|
|
1354
|
+
.replaceWith(t.identifier(prefixedName));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
ClassPrivateProperty(propPath, propState) {
|
|
1359
|
+
handleError(propPath, {
|
|
1360
|
+
errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
|
|
1361
|
+
messageArgs: ['fields'],
|
|
1362
|
+
}, propState);
|
|
1363
|
+
},
|
|
1364
|
+
}, state);
|
|
1365
|
+
state.file.metadata[PRIVATE_METHOD_METADATA_KEY] = transformedNames;
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/*
|
|
1372
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
1373
|
+
* All rights reserved.
|
|
1374
|
+
* SPDX-License-Identifier: MIT
|
|
1375
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
1376
|
+
*/
|
|
1377
|
+
/**
|
|
1378
|
+
* Standalone Babel plugin that reverses the private method transformation by converting
|
|
1379
|
+
* methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
|
|
1380
|
+
* and restoring prefixed MemberExpression properties back to PrivateName nodes.
|
|
1381
|
+
*
|
|
1382
|
+
* This must be registered AFTER @babel/plugin-transform-class-properties so that
|
|
1383
|
+
* class properties are fully transformed before private methods are restored.
|
|
1384
|
+
*
|
|
1385
|
+
* Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
|
|
1386
|
+
* properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
|
|
1387
|
+
* abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
|
|
1388
|
+
*
|
|
1389
|
+
* @see {@link ./private-method-transform.ts} for original transformation
|
|
1390
|
+
*/
|
|
1391
|
+
function reversePrivateMethodTransform({ types: t, }) {
|
|
1392
|
+
// Scoped to this plugin instance's closure. Safe as long as each Babel run creates a
|
|
1393
|
+
// fresh plugin via LwcReversePrivateMethodTransform(); would accumulate across files if
|
|
1394
|
+
// the same instance were ever reused.
|
|
1395
|
+
const reverseTransformedNames = new Set();
|
|
1396
|
+
return {
|
|
1397
|
+
visitor: {
|
|
1398
|
+
ClassMethod(path, state) {
|
|
1399
|
+
const key = path.get('key');
|
|
1400
|
+
// kind: 'method' | 'get' | 'set' - only 'method' is in scope.
|
|
1401
|
+
if (key.isIdentifier() && path.node.kind === 'method') {
|
|
1402
|
+
const methodName = key.node.name;
|
|
1403
|
+
if (methodName.startsWith(PRIVATE_METHOD_PREFIX)) {
|
|
1404
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1405
|
+
// If the method was not transformed by the forward pass, it is a
|
|
1406
|
+
// user-defined method that collides with the reserved prefix.
|
|
1407
|
+
if (!forwardTransformedNames || !forwardTransformedNames.has(methodName)) {
|
|
1408
|
+
const message = DecoratorErrors.PRIVATE_METHOD_NAME_COLLISION.message.replace('{0}', methodName);
|
|
1409
|
+
throw path.buildCodeFrameError(message);
|
|
1410
|
+
}
|
|
1411
|
+
const originalPrivateName = methodName.replace(PRIVATE_METHOD_PREFIX, '');
|
|
1412
|
+
const node = path.node;
|
|
1413
|
+
const classPrivateMethod = t.classPrivateMethod('method', t.privateName(t.identifier(originalPrivateName)), node.params, node.body, node.static);
|
|
1414
|
+
// Properties the t.classPrivateMethod() builder doesn't accept
|
|
1415
|
+
classPrivateMethod.async = node.async;
|
|
1416
|
+
classPrivateMethod.generator = node.generator;
|
|
1417
|
+
classPrivateMethod.computed = node.computed;
|
|
1418
|
+
copyMethodMetadata(node, classPrivateMethod);
|
|
1419
|
+
path.replaceWith(classPrivateMethod);
|
|
1420
|
+
reverseTransformedNames.add(methodName);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
MemberExpression(path, state) {
|
|
1425
|
+
const property = path.node.property;
|
|
1426
|
+
if (!t.isIdentifier(property) || !property.name.startsWith(PRIVATE_METHOD_PREFIX)) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1430
|
+
if (!forwardTransformedNames || !forwardTransformedNames.has(property.name)) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
const originalName = property.name.replace(PRIVATE_METHOD_PREFIX, '');
|
|
1434
|
+
path.get('property').replaceWith(t.privateName(t.identifier(originalName)));
|
|
1435
|
+
},
|
|
1436
|
+
// After all nodes have been visited, verify that every method the forward transform
|
|
1437
|
+
// renamed was also restored by the reverse transform. A mismatch here means an
|
|
1438
|
+
// intermediate plugin (e.g. @babel/plugin-transform-class-properties) removed or
|
|
1439
|
+
// renamed a prefixed method, leaving a mangled name in the final output.
|
|
1440
|
+
Program: {
|
|
1441
|
+
exit(_path, state) {
|
|
1442
|
+
const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
|
|
1443
|
+
if (!forwardTransformedNames) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const missingFromReverse = [];
|
|
1447
|
+
for (const name of forwardTransformedNames) {
|
|
1448
|
+
if (!reverseTransformedNames.has(name)) {
|
|
1449
|
+
missingFromReverse.push(name);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if (missingFromReverse.length > 0) {
|
|
1453
|
+
throw new Error(`Private method transform count mismatch: ` +
|
|
1454
|
+
`forward transformed ${forwardTransformedNames.size} method(s), ` +
|
|
1455
|
+
`but reverse transformed ${reverseTransformedNames.size}. ` +
|
|
1456
|
+
`Missing reverse transforms for: ${missingFromReverse.join(', ')}`);
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
},
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1243
1464
|
/*
|
|
1244
1465
|
* Copyright (c) 2023, salesforce.com, inc.
|
|
1245
1466
|
* All rights reserved.
|
|
@@ -1288,6 +1509,6 @@ function LwcClassTransform(api) {
|
|
|
1288
1509
|
};
|
|
1289
1510
|
}
|
|
1290
1511
|
|
|
1291
|
-
export { LwcClassTransform as default };
|
|
1292
|
-
/** version: 9.0.
|
|
1512
|
+
export { privateMethodTransform as LwcPrivateMethodTransform, reversePrivateMethodTransform as LwcReversePrivateMethodTransform, LwcClassTransform as default };
|
|
1513
|
+
/** version: 9.0.4-alpha.1 */
|
|
1293
1514
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BabelAPI, LwcBabelPluginPass } from './types';
|
|
2
|
+
import type { PluginObj } from '@babel/core';
|
|
3
|
+
/**
|
|
4
|
+
* Standalone Babel plugin that transforms private method identifiers from
|
|
5
|
+
* `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
|
|
6
|
+
*
|
|
7
|
+
* This must be registered BEFORE the main LWC class transform plugin so that
|
|
8
|
+
* private methods are converted to regular methods before decorator and class
|
|
9
|
+
* property processing.
|
|
10
|
+
*
|
|
11
|
+
* Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
|
|
12
|
+
* because the reverse transform has a ClassMethod visitor in the same Babel pass.
|
|
13
|
+
* A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
|
|
14
|
+
* immediately converts back, creating an infinite loop. The manual traverse ensures
|
|
15
|
+
* all forward replacements complete before the reverse visitor sees any ClassMethod.
|
|
16
|
+
*/
|
|
17
|
+
export default function privateMethodTransform({ types: t, }: BabelAPI): PluginObj<LwcBabelPluginPass>;
|
|
18
|
+
//# sourceMappingURL=private-method-transform.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BabelAPI, LwcBabelPluginPass } from './types';
|
|
2
|
+
import type { PluginObj } from '@babel/core';
|
|
3
|
+
/**
|
|
4
|
+
* Standalone Babel plugin that reverses the private method transformation by converting
|
|
5
|
+
* methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
|
|
6
|
+
* and restoring prefixed MemberExpression properties back to PrivateName nodes.
|
|
7
|
+
*
|
|
8
|
+
* This must be registered AFTER @babel/plugin-transform-class-properties so that
|
|
9
|
+
* class properties are fully transformed before private methods are restored.
|
|
10
|
+
*
|
|
11
|
+
* Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
|
|
12
|
+
* properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
|
|
13
|
+
* abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
|
|
14
|
+
*
|
|
15
|
+
* @see {@link ./private-method-transform.ts} for original transformation
|
|
16
|
+
*/
|
|
17
|
+
export default function reversePrivateMethodTransform({ types: t, }: BabelAPI): PluginObj<LwcBabelPluginPass>;
|
|
18
|
+
//# sourceMappingURL=reverse-private-method-transform.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface LwcBabelPluginOptions {
|
|
|
14
14
|
instrumentation?: InstrumentationObject;
|
|
15
15
|
apiVersion?: number;
|
|
16
16
|
enableSyntheticElementInternals?: boolean;
|
|
17
|
+
enablePrivateMethods?: boolean;
|
|
17
18
|
componentFeatureFlagModulePath?: string;
|
|
18
19
|
}
|
|
19
20
|
export interface LwcBabelPluginPass extends PluginPass {
|
package/dist/utils.d.ts
CHANGED
|
@@ -21,5 +21,12 @@ declare function getEngineImportSpecifiers(path: NodePath): ImportSpecifier[];
|
|
|
21
21
|
declare function handleError(source: NodePath<types.Node>, decoratorErrorOpts: DecoratorErrorOptions, state: LwcBabelPluginPass): void;
|
|
22
22
|
declare function incrementMetricCounter(metric: CompilerMetrics, state: LwcBabelPluginPass): void;
|
|
23
23
|
declare function isErrorRecoveryMode(state: LwcBabelPluginPass): boolean;
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
|
|
26
|
+
* These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
|
|
27
|
+
* so they must be transferred manually after node creation. Both the forward and reverse
|
|
28
|
+
* private-method transforms use this to maintain round-trip parity.
|
|
29
|
+
*/
|
|
30
|
+
declare function copyMethodMetadata(source: types.ClassMethod | types.ClassPrivateMethod, target: types.ClassMethod | types.ClassPrivateMethod): void;
|
|
31
|
+
export { isClassMethod, isGetterClassMethod, isSetterClassMethod, getEngineImportSpecifiers, handleError, incrementMetricCounter, isErrorRecoveryMode, copyMethodMetadata, };
|
|
25
32
|
//# sourceMappingURL=utils.d.ts.map
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
|
|
5
5
|
],
|
|
6
6
|
"name": "@lwc/babel-plugin-component",
|
|
7
|
-
"version": "9.0.
|
|
7
|
+
"version": "9.0.4-alpha.1",
|
|
8
8
|
"description": "Babel plugin to transform a LWC module",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"lwc"
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@babel/helper-module-imports": "7.28.6",
|
|
55
|
-
"@lwc/errors": "9.0.
|
|
56
|
-
"@lwc/shared": "9.0.
|
|
55
|
+
"@lwc/errors": "9.0.4-alpha.1",
|
|
56
|
+
"@lwc/shared": "9.0.4-alpha.1",
|
|
57
57
|
"line-column": "~1.0.2"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|