-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decorators #7542
Decorators #7542
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/8074/ |
import syntaxDecorators from "../lib"; | ||
|
||
describe("legacy option", function() { | ||
const oldSyntax = "@dec export class Foo {}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/atlassian/jest-in-case could make those assertions nicely grouped
The I only implemented decorators for public methods. Since decorators transpilation is highly copuled to class fields and private properties, I think that we should have a single plugin for the three features: {
"plugins": [
["@babel/plugin-proposal-enhanced-classes", {
"decorators": true,
"fields": false,
"privateMethods": true,
}]
]
} We should then deprecate |
Yeah it might depend on https://github.com/zenparsing/js-classes-1.1 too. I was thinking we should make things like |
86e80fd
to
430f9c0
Compare
After creating `@babel/plugin-proposal-enhanced-classes` with external-helpersconst Foo = babelHelpers.enhanceClass(function(
_defineClass,
_initializeClass,
_initializeInstance
) {
_defineClass(
class Foo {
constructor() {
_initializeInstance(this);
}
},
[
{
kind: "field",
key: "num",
value() {
return 2;
},
},
]
);
_initializeClass();
}); `@babel/plugin-proposal-enhanced-classes` without external-helpersfunction _enhanceClass(factory) { var internalSlots = { F: null, elements: null, finishers: null }; factory(internalSlots, function defineClass(F, definitions) { var elements = definitions.map(_createElementDescriptor); elements = _coalesceClassElements(elements); internalSlots.F = F; internalSlots.elements = elements; }, function initializeClassElements() { _initializeClassElements(internalSlots); }, function initializeInsanceElements(O) { _initializeInstanceElements(O, internalSlots); }); return internalSlots.F; }
function _createElementDescriptor(def) { let descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: def.key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
function _coalesceClassElements(elements) { const newElements = []; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var index = newElements.findIndex(function (other) { return other.kind === element.kind && other.key === element.key && other.placement === element.placement; }); if (element.kind === "method" && index !== -1) { var other = newElements[index]; if (element.decorators && element.decorators.length > 0) { if (other.decorators && other.decorators.length > 0) { throw new ReferenceError(); } other.decorators = element.decorators; } if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { other.descriptor = element.descriptor; } else { _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
function _initializeClassElements(internalSlots) { var F = internalSlots.F; var proto = F.prototype; internalSlots.elements.forEach(function (element) { if (element.kind === "method") { var receiver = element.placement === "static" ? F : proto; _defineClassElement(receiver, element); } }); internalSlots.elements.forEach(function (element) { var placement = element.placement; if (element.kind === "field" && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; _defineClassElement(receiver, element); } }); }
function _initializeInstanceElements(O, internalSlots) { internalSlots.elements.forEach(function (element) { if (element.kind === "method" && element.placement === "own") { _defineClassElement(O, element); } }); internalSlots.elements.forEach(function (element) { if (element.kind === "field" && element.placement === "own") { _defineClassElement(O, element); } }); }
function _defineClassElement(receiver, element) { if (element.kind === "field") { var initializer = element.initializer; element.descriptor.value = initializer === void 0 ? void 0 : initializer.call(receiver); } Object.defineProperty(receiver, element.key, element.descriptor); }
const Foo = _enhanceClass(function(
_defineClass,
_initializeClass,
_initializeInstance
) {
_defineClass(
class Foo {
constructor() {
_initializeInstance(this);
}
},
[
{
kind: "field",
key: "num",
value() {
return 2;
},
},
]
);
_initializeClass();
}); `@babel/plugin-transform-class-properties`class Foo {
constructor() {
Object.defineProperty(this, "num", {
configurable: true,
enumerable: true,
writable: true,
value: 2,
});
}
} I think that we should keep the two plugins separated, but let I haven't tested private properties yet, but I think that they will have the same problem. |
d08310a
to
ceb2ddf
Compare
Weird CircleCI error:
|
Thank you, I restarted it. |
Will @babel-bot update REPL? |
Yes, the link in the first comment is always updated |
I still need to add some tests (e.g. finishers), but this PR can be reviewed |
530f8cd
to
3ec60b3
Compare
Next step: #7821 After that PR, I will update Babylon again to reflect the last changes (tc39/proposal-decorators#81 (comment) and tc39/proposal-decorators#69 (comment)). |
Things getting complicated as I see… I was interested only in simple class or function decorators especifically only as React HoC… So I have questions:
Thank you for your attention on this, I see and really appreciate — it's really hard work |
* Add decoratorsBeforeExport to the syntax plugin * Require legacy: true, like in the transform plugin
8f5a7f6
to
ba0a0c8
Compare
I have merged the decorators PRs here so that they can be tested together in the repl (https://babeljs.io/repl/build/8074). @langpavel Decorators will be hard to optimize, but I hope that js engines will figure out how to optimize them. |
I'm closing this, since now it is the same as #7976 |
EDIT by @hzoo: REPL link https://babeljs.io/repl/build/8074/ - need to turn on Stage 2
I'm working on #6107. Since that PR has become quite old (and there are different things that needs to be done) I'm not rebasing it, but I will just copy-paste the needed code.
Then I will set both me and @peey as the PR authors (I have no idea of how to do it, but I'm sure that it is possible 🤣).
TODO:
NOTE: The
legacy-transformer.js
file contains the code that was peviously inindex.js
, I didn't change it.Breaking change!
I added a new option to keep both the new and legacy decorators in the same plugin. By default, the new proposal is used but it can be disabled with the
legacy: true
option (accepted by-syntax-
and-proposal-
). In thestage-0,1,2
presets this option is calleddecoratorsLegacy
.