Skip to content

Commit a3519b8

Browse files
committed
chore: save
1 parent da10730 commit a3519b8

File tree

9 files changed

+209
-5
lines changed

9 files changed

+209
-5
lines changed

packages/@lwc/babel-plugin-component/src/decorators/index.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { generateError, isClassMethod, isGetterClassMethod, isSetterClassMethod
1212
import api from './api';
1313
import wire from './wire';
1414
import track from './track';
15+
import privateDecorator from './private';
1516
import type { BabelAPI, BabelTypes, LwcBabelPluginPass } from '../types';
1617
import type { Node, types, Visitor, NodePath } from '@babel/core';
1718
import type { ClassBodyItem, ImportSpecifier, LwcDecoratorName } from './types';
1819

19-
const DECORATOR_TRANSFORMS = [api, wire, track];
20+
const DECORATOR_TRANSFORMS = [api, wire, track, privateDecorator];
2021
const AVAILABLE_DECORATORS = DECORATOR_TRANSFORMS.map((transform) => transform.name).join(', ');
2122

2223
export type DecoratorType = (typeof DECORATOR_TYPES)[keyof typeof DECORATOR_TYPES];
@@ -244,6 +245,7 @@ function getMetadataObjectPropertyList(
244245
...api.transform(t, decoratorMetas, classBodyItems),
245246
...track.transform(t, decoratorMetas),
246247
...wire.transform(t, decoratorMetas),
248+
...privateDecorator.transform(t, decoratorMetas, classBodyItems),
247249
];
248250

249251
const fieldNames = classBodyItems
@@ -316,6 +318,71 @@ function decorators({ types: t }: BabelAPI): Visitor<LwcBabelPluginPass> {
316318

317319
decoratorPaths.forEach((path) => path.remove());
318320

321+
// Lower instance class fields into constructor assignments BEFORE registerDecorators replacement
322+
const fieldProps = classBodyItems
323+
.filter((f) => f.isClassProperty({ static: false, computed: false }))
324+
.filter((f) => !(f.node as types.ClassProperty).decorators);
325+
326+
const assigns: types.Statement[] = [];
327+
for (const fp of fieldProps) {
328+
const id = (fp.node.key as types.Identifier).name;
329+
const init = fp.node.value ?? t.identifier('undefined');
330+
assigns.push(
331+
t.expressionStatement(
332+
t.assignmentExpression(
333+
'=',
334+
t.memberExpression(t.thisExpression(), t.identifier(id)),
335+
init
336+
)
337+
)
338+
);
339+
fp.remove(); // remove native class field
340+
}
341+
342+
// Find or create constructor
343+
const ctorPath = classBodyItems.find((p) =>
344+
p.isClassMethod({ kind: 'constructor' })
345+
) as NodePath<types.ClassMethod> | undefined;
346+
if (assigns.length) {
347+
if (ctorPath) {
348+
// insert after super(...)
349+
const body = ctorPath.get('body.body') as NodePath<types.Statement>[];
350+
const superIndex = body.findIndex(
351+
(b) =>
352+
b.isExpressionStatement() &&
353+
b.node.expression.type === 'CallExpression' &&
354+
(b.node.expression.callee as any).type === 'Super'
355+
);
356+
const insertIndex = superIndex >= 0 ? superIndex + 1 : 0;
357+
if (body[insertIndex]) {
358+
body[insertIndex].insertBefore(assigns);
359+
} else {
360+
ctorPath.get('body').pushContainer('body', assigns);
361+
}
362+
} else {
363+
const needsSuper = node.superClass !== null;
364+
const ctorBodyStmts: types.Statement[] = needsSuper
365+
? [
366+
t.expressionStatement(
367+
t.callExpression(t.super(), [
368+
t.spreadElement(t.identifier('args')),
369+
])
370+
),
371+
...assigns,
372+
]
373+
: assigns;
374+
const params = needsSuper ? [t.restElement(t.identifier('args'))] : [];
375+
const ctor = t.classMethod(
376+
'constructor',
377+
t.identifier('constructor'),
378+
params,
379+
t.blockStatement(ctorBodyStmts)
380+
);
381+
path.get('body').pushContainer('body', ctor);
382+
}
383+
}
384+
385+
// Now perform registerDecorators transform
319386
const isAnonymousClassDeclaration =
320387
path.isClassDeclaration() && !path.get('id').isIdentifier();
321388
const shouldTransformAsClassExpression =
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) 2024, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
import { LWC_PACKAGE_EXPORTS } from '../../constants';
8+
9+
import validate from './validate';
10+
import transform from './transform';
11+
12+
const { PRIVATE_DECORATOR } = LWC_PACKAGE_EXPORTS;
13+
14+
export default {
15+
name: PRIVATE_DECORATOR,
16+
validate,
17+
transform,
18+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2024, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
import type { BabelTypes } from '../../types';
8+
import type { DecoratorMeta } from '../index';
9+
10+
function isPrivateDecorator(decorator: DecoratorMeta) {
11+
return decorator.name === 'privateField';
12+
}
13+
14+
export default function transform(
15+
t: BabelTypes,
16+
decoratorMetas: DecoratorMeta[],
17+
classBodyItems: any[]
18+
) {
19+
const privateDecoratorMetas = decoratorMetas.filter(isPrivateDecorator);
20+
21+
if (privateDecoratorMetas.length === 0) {
22+
return [];
23+
}
24+
25+
// Transform each @privateField decorated member to temporary naming convention
26+
privateDecoratorMetas.forEach(({ path, propertyName, decoratedNodeType }) => {
27+
// Find the class member that this decorator was attached to
28+
const classMember = path.parentPath;
29+
if (!classMember) return;
30+
31+
// Transform the property name to temporary private naming convention
32+
if (decoratedNodeType === 'property' || decoratedNodeType === 'method') {
33+
const key = classMember.get('key');
34+
if (key && !Array.isArray(key) && key.isIdentifier()) {
35+
// Transform the identifier to use temporary private naming convention
36+
const tempPrivateName = t.identifier(`__private_${propertyName}`);
37+
key.replaceWith(tempPrivateName);
38+
}
39+
}
40+
});
41+
42+
// Transform access patterns within the class to use temporary naming convention
43+
classBodyItems.forEach((item) => {
44+
if (item.isClassMethod() || item.isClassProperty()) {
45+
const privateFieldNames = privateDecoratorMetas.map((d) => d.propertyName);
46+
47+
item.traverse({
48+
MemberExpression(memberPath: any) {
49+
const { object, property } = memberPath.node;
50+
if (
51+
t.isThisExpression(object) &&
52+
t.isIdentifier(property) &&
53+
privateFieldNames.includes(property.name)
54+
) {
55+
// Transform this.propertyName to this.__private_propertyName
56+
const tempPrivateName = t.identifier(`__private_${property.name}`);
57+
memberPath.replaceWith(t.memberExpression(object, tempPrivateName, false));
58+
}
59+
},
60+
});
61+
}
62+
});
63+
64+
// Return empty array since we don't need any metadata for private fields
65+
return [];
66+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2024, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
import { DecoratorErrors } from '@lwc/errors';
8+
import { generateError } from '../../utils';
9+
import type { LwcBabelPluginPass } from '../../types';
10+
import type { DecoratorMeta } from '../index';
11+
12+
function isPrivateDecorator(decorator: DecoratorMeta) {
13+
return decorator.name === 'privateField';
14+
}
15+
16+
function validate(decorators: DecoratorMeta[], state: LwcBabelPluginPass) {
17+
decorators.filter(isPrivateDecorator).forEach(({ path, decoratedNodeType }) => {
18+
// @privateField can only be used on class properties and methods
19+
if (decoratedNodeType !== 'property' && decoratedNodeType !== 'method') {
20+
throw generateError(
21+
path,
22+
{
23+
errorInfo: DecoratorErrors.INVALID_DECORATOR,
24+
messageArgs: ['@privateField', 'class properties and methods'],
25+
},
26+
state
27+
);
28+
}
29+
});
30+
}
31+
32+
export default validate;

packages/@lwc/compiler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@babel/plugin-transform-async-to-generator": "7.27.1",
5252
"@babel/plugin-transform-class-properties": "7.27.1",
5353
"@babel/plugin-transform-object-rest-spread": "7.28.0",
54+
"@babel/plugin-transform-private-methods": "7.27.1",
5455
"@locker/babel-plugin-transform-unforgeables": "0.22.0",
5556
"@lwc/babel-plugin-component": "8.20.2",
5657
"@lwc/errors": "8.20.2",

packages/@lwc/compiler/src/transformers/javascript.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import * as babel from '@babel/core';
88
import babelAsyncGeneratorFunctionsPlugin from '@babel/plugin-transform-async-generator-functions';
99
import babelAsyncToGenPlugin from '@babel/plugin-transform-async-to-generator';
10-
import babelClassPropertiesPlugin from '@babel/plugin-transform-class-properties';
1110
import babelObjectRestSpreadPlugin from '@babel/plugin-transform-object-rest-spread';
1211
import lockerBabelPluginTransformUnforgeables from '@locker/babel-plugin-transform-unforgeables';
1312
import lwcClassTransformPlugin, { type LwcBabelPluginOptions } from '@lwc/babel-plugin-component';
@@ -54,7 +53,8 @@ export default function scriptTransform(
5453

5554
const plugins: babel.PluginItem[] = [
5655
[lwcClassTransformPlugin, lwcBabelPluginOptions],
57-
[babelClassPropertiesPlugin, { loose: true }],
56+
// [babelClassPropertiesPlugin, { loose: true }],
57+
// [babelPrivateMethodPlugin, { loose: true }]
5858
];
5959

6060
if (!isAPIFeatureEnabled(APIFeature.DISABLE_OBJECT_REST_SPREAD_TRANSFORMATION, apiVersion)) {

packages/@lwc/compiler/src/typings/babel.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@ declare module '@babel/plugin-transform-async-to-generator' {
2020
const props: any;
2121
export = props;
2222
}
23+
24+
declare module '@babel/plugin-transform-private-methods' {
25+
const props: any;
26+
export = props;
27+
}

playground/src/modules/x/counter/counter.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@ export default class extends LightningElement {
1010
}
1111
decrement() {
1212
this.counter--;
13+
this.#something();
14+
}
15+
#something() {
16+
// eslint-disable-next-line no-console
17+
console.log(`Private`);
1318
}
1419
}

yarn.lock

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,14 @@
853853
dependencies:
854854
"@babel/helper-plugin-utils" "^7.27.1"
855855

856+
"@babel/plugin-transform-private-methods@7.27.1":
857+
version "7.27.1"
858+
resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af"
859+
integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==
860+
dependencies:
861+
"@babel/helper-create-class-features-plugin" "^7.27.1"
862+
"@babel/helper-plugin-utils" "^7.27.1"
863+
856864
"@babel/plugin-transform-private-methods@^7.25.9":
857865
version "7.25.9"
858866
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
@@ -2244,9 +2252,11 @@
22442252

22452253
"@lwc/eslint-plugin-lwc-internal@link:./scripts/eslint-plugin":
22462254
version "0.0.0"
2255+
uid ""
22472256

22482257
"@lwc/test-utils-lwc-internals@link:./scripts/test-utils":
22492258
version "0.0.0"
2259+
uid ""
22502260

22512261
"@napi-rs/wasm-runtime@0.2.4":
22522262
version "0.2.4"
@@ -8617,7 +8627,7 @@ http-assert@^1.3.0:
86178627

86188628
http-cache-semantics@3.8.1, http-cache-semantics@4.1.1, http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1:
86198629
version "4.1.1"
8620-
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
8630+
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
86218631
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
86228632

86238633
http-errors@2.0.0, http-errors@^2.0.0:
@@ -12714,7 +12724,7 @@ semver-truncate@^1.1.2:
1271412724

1271512725
semver@7.6.0, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^6.3.0, semver@^6.3.1, semver@^7.1.1, semver@^7.3.2, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.2:
1271612726
version "7.6.0"
12717-
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
12727+
resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
1271812728
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
1271912729
dependencies:
1272012730
lru-cache "^6.0.0"

0 commit comments

Comments
 (0)