Skip to content
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

Lift single-expression helper restriction and use hoistable function declarations wherever possible in helpers #5706

Merged
merged 2 commits into from
Sep 13, 2017

Conversation

loganfsmyth
Copy link
Member

Q A
Patch: Bug Fix? Y
Major: Breaking Change? Y
Minor: New Feature? N
Deprecations?
Spec Compliancy?
Tests Added/Pass?
Fixed Tickets
License MIT
Doc PR
Dependency Changes

Essentially, I rewrote the implementation of Babel's helper system to remove the restriction of helpers being a single expression. Instead they are treated as standalone modules, and whatever their default export is will end up being the name referenced inside the user's code.

The main goal here is that now helpers don't need to be a single expression, meaning they can use function declarations more consistently, which means that this removes the need for _blockHoist on helpers.

I was in the code, so I ended up adding a general fix for ember-cli/ember-cli#7004 / #5536, but that was not the main goal.

@loganfsmyth loganfsmyth requested a review from Jessidhia May 5, 2017 09:12
@mention-bot
Copy link

@loganfsmyth, thanks for your PR! By analyzing the history of the files in this pull request, we identified @zertosh, @existentialism and @spicyj to be potential reviewers.

@codecov
Copy link

codecov bot commented May 5, 2017

Codecov Report

Merging #5706 into 7.0 will increase coverage by 0.09%.
The diff coverage is 88.18%.

Impacted file tree graph

@@            Coverage Diff             @@
##              7.0    #5706      +/-   ##
==========================================
+ Coverage   84.57%   84.67%   +0.09%     
==========================================
  Files         282      282              
  Lines        9857     9935      +78     
  Branches     2766     2785      +19     
==========================================
+ Hits         8337     8412      +75     
+ Misses       1000      994       -6     
- Partials      520      529       +9
Impacted Files Coverage Δ
...l-plugin-transform-async-to-generator/src/index.js 100% <ø> (ø) ⬆️
packages/babel-helpers/src/helpers.js 100% <100%> (ø) ⬆️
...ckages/babel-core/src/transformation/file/index.js 87.14% <100%> (ø) ⬆️
...ges/babel-core/src/tools/build-external-helpers.js 94.11% <100%> (ø) ⬆️
packages/babel-helpers/src/index.js 82.75% <82.14%> (-2.96%) ⬇️
...bel-plugin-transform-es2015-classes/src/vanilla.js 90.98% <0%> (+0.85%) ⬆️
packages/babel-traverse/src/path/context.js 85.34% <0%> (+0.86%) ⬆️
packages/babel-traverse/src/path/modification.js 74.75% <0%> (+0.97%) ⬆️
packages/babel-traverse/src/scope/index.js 80.23% <0%> (+1.42%) ⬆️
packages/babel-helper-call-delegate/src/index.js 68% <0%> (+4%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 899a754...34de640. Read the comment docs.

}

helpers.typeof = defineHelper(`
export default function _typeof(obj) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't need the _ prefix anymore right?

Though I guess this one does as it's a keyword.


helpers.typeof = defineHelper(`
export default function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can somehow avoid the typeof helper being defined with the typeof helper...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd have better logic to not have helpers process eachother, maybe?

if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) { return typeof obj; };
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(unrelated) wouldn't instanceof be more reliable than .constructor

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah not sure if this was done for perf maybe?

return function createRawReactElement (type, props, key, children) {
var defaultProps = type && type.defaultProps;
var childrenLength = arguments.length - 3;
export default function _createRawReactElement(type, props, key, children) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helpers shouldn't need the _ anymore; if they should be prepended when inlined, then maybe change the inlining logic to ensure at least one _

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this so things were less likely to need to be renamed, and I still kind of feel like that is good? Especially since helpers can have multiple functions now, it's nice to make collision less likely so things keep their predefined name.

}
} else if (!props) {
props = defaultProps || {};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this could be simplified to

props = defaultProps || childrenLength > 0
  ? Object.assign({}, defaultProps, props)
  : (defaultProps || {})

but alas, we probably can't use Object.assign in helpers 😢

function _defineProperties(target, props) {
for (var i = 0; i < props.length; i ++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It defaults to false if omitted, so maybe this line is unnecessary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep changes to helpers separate from this refactor.

@@ -408,57 +418,53 @@ helpers.interopRequireWildcard = template(`
newObj.default = obj;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var newObj = { "default": obj }; 😆

helpers.toConsumableArray = template(`
(function (arr) {
helpers.toConsumableArray = defineHelper(`
export default function _toConsumableArray(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought creating arrays with an initial length was a bad idea in JS

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I've heard that before. Not really looking to change the helpers in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6233 heh

},
AssignmentExpression(child) {
const left = child.get("left");
if (!left.isIdentifier()) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it ok to skip destructuring here? maybe throw to make sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, would be better to error.

@peey
Copy link
Contributor

peey commented Aug 1, 2017

If helpers can be imported from within other helpers, then feature request #6030 is fulfilled as well

@babel-bot
Copy link
Collaborator

babel-bot commented Sep 1, 2017

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/4893/

const ast = helpers[name]();
return t.file(t.program(Array.isArray(ast) ? ast : [ast]));
};
if (!fn) throw new ReferenceError(`Unknown helper ${name}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. It's always a function, you just defined it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I 💯 put this in the wrong place, not sure what I was thinking.


traverse(file, {
ImportDeclaration() {
throw new Error();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw the #buildCodeFrameError, please.


if (decl.isFunctionDeclaration()) {
if (!decl.node.id) {
throw new Error(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw the #buildCodeFrameError, please.

},
});

traverse(file, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing multiple traversals?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helpers are such tiny ASTs that it didn't seem like it would be an issue to traverse multiple times, especially since helpers are memoized, so at most this is called once per-helper-per-file.


return fn().expression;
for (; path; path = path.parentPath) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use path.parentPath instead of path in the condition to avoid pushing and then popping (line 14) the last node.

let exportPath;
const exportBindingAssignments = [];

traverse(file, {
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo Sep 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing a full traversal, you could only iterate over file.program.body.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to create the NodePath, but yeah fair. I added a path.skip() to ignore anything isn't the root import/exports.

@loganfsmyth loganfsmyth merged commit 634c750 into babel:master Sep 13, 2017
@loganfsmyth loganfsmyth deleted the modular-helpers branch September 13, 2017 05:15
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants