Skip to content

Commit

Permalink
feat: track reform
Browse files Browse the repository at this point in the history
RFC for this change: salesforce/lwc-rfcs#4

The logic for this PR is as follows:

When the compiler compiles the class, it extracts any field that is not decorated with @api, @track or @wire, and pass the metadata through the registerDecorators call.

Given that in order to observe a field, we need a vm, and we need to register the fields of all classes and save it in the decoratorsMeta, we need to wait until we create the ComponentDefinition to know which classes will be used as components.

For every class field we will observe in a Component, we will create a getter and a setter in the prototype of the ComponentDefinition (and the class inheritance chain until BaseLightningElement).

No change is needed to the logic in the engine.
  • Loading branch information
jodarove committed Aug 7, 2019
1 parent a40d174 commit 557c49e
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ describe('Transform property', () => {
outer: {
config: 0
}
}
},
fields: ["a"]
});
export default _registerComponent(Outer, {
Expand Down Expand Up @@ -333,7 +334,8 @@ describe('Transform property', () => {
b: {
config: 3
}
}
},
fields: ["_a", "_b"]
});
export default _registerComponent(Test, {
Expand Down Expand Up @@ -405,7 +407,8 @@ describe('Transform property', () => {
config: 3
}
},
publicMethods: ["m1"]
publicMethods: ["m1"],
fields: ["privateProp", "ctor"]
});
export default _registerComponent(Text, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2018, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
const pluginTest = require('./utils/test-transform').pluginTest(require('../index'));

describe('observable fields', () => {
pluginTest(
'should be added to the registerComponentCall when a field is not decorated with @api, @track or @wire',
`
import { api, wire, track, createElement } from 'lwc';
export default class Test {
state;
@track foo;
@track bar;
@api label;
record = {
value: 'test'
};
@api
someMethod() {}
@wire(createElement) wiredProp;
}
`,
{
output: {
code: `
import { registerDecorators as _registerDecorators } from "lwc";
import _tmpl from "./test.html";
import { registerComponent as _registerComponent } from "lwc";
import { createElement } from "lwc";
class Test {
constructor() {
this.state = void 0;
this.foo = void 0;
this.bar = void 0;
this.label = void 0;
this.record = {
value: "test"
};
this.wiredProp = void 0;
}
someMethod() {}
}
_registerDecorators(Test, {
publicProps: {
label: {
config: 0
}
},
publicMethods: ["someMethod"],
wire: {
wiredProp: {
adapter: createElement
}
},
track: {
foo: 1,
bar: 1
},
fields: ["state", "record"]
});
export default _registerComponent(Test, {
tmpl: _tmpl
});
`,
},
}
);

pluginTest(
'should transform export default that is not a class',
`
const DATA_FROM_NETWORK = [
{
id: '1',
},
{
id: '2',
},
];
export default DATA_FROM_NETWORK;
`,
{
output: {
code: `
import _tmpl from "./test.html";
import { registerComponent as _registerComponent } from "lwc";
const DATA_FROM_NETWORK = [
{
id: "1"
},
{
id: "2"
}
];
export default _registerComponent(DATA_FROM_NETWORK, {
tmpl: _tmpl
});
`,
},
}
);

pluginTest(
'should add observed fields in class expression',
`
import { api, wire, track, createElement } from 'lwc';
const Test = class {
state;
@track foo;
@track bar;
@api label;
record = {
value: 'test'
};
@api
someMethod() {}
@wire(createElement) wiredProp;
}
const foo = Test;
export default foo;
`,
{
output: {
code: `
import _tmpl from "./test.html";
import { registerComponent as _registerComponent } from "lwc";
import { registerDecorators as _registerDecorators } from "lwc";
import { createElement } from "lwc";
const Test = _registerDecorators(
class {
constructor() {
this.state = void 0;
this.foo = void 0;
this.bar = void 0;
this.label = void 0;
this.record = {
value: "test"
};
this.wiredProp = void 0;
}
someMethod() {}
},
{
publicProps: {
label: {
config: 0
}
},
publicMethods: ["someMethod"],
wire: {
wiredProp: {
adapter: createElement
}
},
track: {
foo: 1,
bar: 1
},
fields: ["state", "record"]
}
);
const foo = Test;
export default _registerComponent(foo, {
tmpl: _tmpl
});
`,
},
}
);
});
43 changes: 39 additions & 4 deletions packages/@lwc/babel-plugin-component/src/post-process/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,42 @@ module.exports = function postProcess({ types: t }) {
return metaPropertyList;
}

function collectObservedFields(body, decoratedProperties) {
const mappers = {
ObjectExpression: ({ properties }) => properties.map(({ key: { name } }) => name),
ArrayExpression: ({ elements }) => elements.map(({ value }) => value),
};

const decoratedIdentifiers = decoratedProperties
.map(({ value }) => mappers[value.type](value))
.reduce((acc, identifiers) => acc.concat(identifiers), []);

const nonDecoratedFields = body
.get('body')
.filter(
path =>
t.isClassProperty(path.node) &&
!isLWCNode(path.node) &&
!(decoratedIdentifiers.indexOf(path.node.key.name) >= 0)
)
.map(path => path.node.key.name);

return nonDecoratedFields.length
? t.objectProperty(t.identifier('fields'), t.valueToNode(nonDecoratedFields))
: null;
}

function collectMetaPropertyList(klassBody) {
const metaPropertyList = collectDecoratedProperties(klassBody);
const observableFields = collectObservedFields(klassBody, metaPropertyList);

if (observableFields) {
metaPropertyList.push(observableFields);
}

return metaPropertyList;
}

function createRegisterDecoratorsCall(path, klass, props) {
const id = moduleImports.addNamed(path, REGISTER_DECORATORS_ID, 'lwc');

Expand Down Expand Up @@ -100,8 +136,8 @@ module.exports = function postProcess({ types: t }) {
ClassExpression(path) {
const { node } = path;
if (!node[LWC_POST_PROCCESED]) {
const body = path.get('body');
const metaPropertyList = collectDecoratedProperties(body);
const metaPropertyList = collectMetaPropertyList(path.get('body'));

if (metaPropertyList.length) {
path.replaceWith(createRegisterDecoratorsCall(path, node, metaPropertyList));
}
Expand All @@ -111,8 +147,7 @@ module.exports = function postProcess({ types: t }) {
// Decorator collector for class declarations
ClassDeclaration(path) {
const { node } = path;
const body = path.get('body');
const metaPropertyList = collectDecoratedProperties(body);
const metaPropertyList = collectMetaPropertyList(path.get('body'));

if (metaPropertyList.length) {
const statementPath = path.getStatementParent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
import _possibleConstructorReturn from '@babel/runtime/helpers/possibleConstructorReturn';
import _getPrototypeOf from '@babel/runtime/helpers/getPrototypeOf';
import _inherits from '@babel/runtime/helpers/inherits';
import { registerDecorators } from 'lwc';
function _templateObject3() {
var data = _taggedTemplateLiteral(["wow\naB", " ", ""], ["wow\\naB", " ", ""]);
_templateObject3 = function _templateObject3() {
Expand Down Expand Up @@ -212,4 +213,7 @@ var Bar = function Bar() {
_classCallCheck(this, Bar);
__setKey(this, "bar", "foo");
};
registerDecorators(Bar, {
fields: ["bar"]
});
export { Bar, Test, literal, obj1, obj2, t, test };
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { registerDecorators } from 'lwc';
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// babel-plugin-check-es2015-constants
Expand Down Expand Up @@ -109,6 +110,8 @@ class Bar {
}

}

registerDecorators(Bar, {
fields: ["bar"]
});
export { Bar, Test, literal, obj1, obj2, t, test };

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { registerTemplate, registerComponent, LightningElement } from 'lwc';
import { registerTemplate, registerComponent, LightningElement, registerDecorators } from 'lwc';

function tmpl($api, $cmp, $slotset, $ctx) {
const {
Expand All @@ -21,6 +21,9 @@ class ClassAndTemplate extends LightningElement {
this.counter = 0;
}
}
registerDecorators(ClassAndTemplate, {
fields: ["t"]
});
var typescript = registerComponent(ClassAndTemplate, {
tmpl: _tmpl
});
Expand Down
4 changes: 4 additions & 0 deletions packages/@lwc/engine/src/framework/decorators/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ export interface RegisterDecoratorMeta {
readonly publicProps?: PropsDef;
readonly track?: TrackDef;
readonly wire?: WireHash;
readonly fields?: string[];
}

export interface DecoratorMeta {
wire: WireHash | undefined;
track: TrackDef;
props: PropsDef;
methods: MethodDef;
fields: string[] | undefined;
}

const signedDecoratorToMetaMap: Map<ComponentConstructor, DecoratorMeta> = new Map();
Expand All @@ -72,11 +74,13 @@ export function registerDecorators(
const methods = getPublicMethodsHash(Ctor, meta.publicMethods);
const wire = getWireHash(Ctor, meta.wire);
const track = getTrackHash(Ctor, meta.track);
const fields = meta.fields;
signedDecoratorToMetaMap.set(Ctor, {
props,
methods,
wire,
track,
fields,
});
for (const propName in props) {
decoratorMap[propName] = apiDecorator;
Expand Down
Loading

0 comments on commit 557c49e

Please sign in to comment.