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

refactor mark declaration, add __PURE__ comments (#286) #309

Merged
merged 5 commits into from Aug 15, 2017
Merged

refactor mark declaration, add __PURE__ comments (#286) #309

merged 5 commits into from Aug 15, 2017

Conversation

modosc
Copy link
Contributor

@modosc modosc commented Aug 4, 2017

(see also #301 for old discussion)

with the current code, this:

export function* test() {}
export function* abc() {}

compiles to this:

var _marked = [test, abc].map(regeneratorRuntime.mark);

export function test() {
  return regeneratorRuntime.wrap(function test$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked[0], this);
}

export function abc() {
  return regeneratorRuntime.wrap(function abc$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked[1], this);
}

because of the [test, abc].map call uglifyjs won't strip out test or abc if they're never actually imported (webpack does correctly identify them as non-imported though).

this pr changes two things:

  1. the map call is unrolled and each call to regeneratorRuntime.mark has the magic #__PURE__ comment added to it so that uglify knows it can drop the code:
var _marked = /*#__PURE__*/regeneratorRuntime.mark(test),
    _marked2 = /*#__PURE__*/regeneratorRuntime.mark(abc);
export function test() {
  return regeneratorRuntime.wrap(function test$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked, this);
}
export function abc() {
  return regeneratorRuntime.wrap(function abc$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked2, this);
}
  1. the magic comment is also added to function expressions, so this:
var foo = function* (){}

is compiled to this:

var foo = /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
  return regeneratorRuntime.wrap(function _callee$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _callee, this);
});

i added tests to verify the new variable declaration scheme. i haven't been able to get recast to return comments to me in a test though - i've verified that this works via bin/regenerator (to verify the comments are there) and by using it in my project with uglifyjs and verifying that unused generators are being stripped out.

)
]);

info.decl = t.variableDeclaration("var", [])
Copy link
Contributor

Choose a reason for hiding this comment

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

this looks odd, should it stay here in the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's basically the same functionality as before which was to memoize the place the declarations were to go - i guess since it's so much smaller now it could go in the caller insetad?

});
});

// TODO - how do i get recast to return comments?
Copy link
Contributor

Choose a reason for hiding this comment

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

i aint sure how what u are trying to achieve here exactly, ive managed to access comments like this:

recast.parse('var _marked = /*#__PURE__*/regeneratorRuntime.mark(test)').program.body[0].declarations[0].init.comments

maybe that helps you somehow

const code = recast.print(transform(ast)).code;
assert.notStrictEqual(
code.indexOf(
'var a = /*#__PURE__*/Object.defineProperty( /*#__PURE__*/regeneratorRuntime.mark'
Copy link
Contributor

Choose a reason for hiding this comment

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

why the Object.defineProperty call here? didnt we ditch it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, copy pasta. will fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just updated all the tests, should be clearer now.


// this can change from pass to pass if something else is altering our body
// so we have to look it up every time :/
let declarationsIndex = pp.node.body.findIndex((p) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we could gather those declarations in context instead of inserting them right away and insert them all at once in program's exit visitor?

aint sure if thats better, just an idea

Copy link
Contributor Author

Choose a reason for hiding this comment

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

happy to look into this, it's probably best to not have to traverse the tree downward every time. i'm new to babel/ast stuff so it may take a bit to sort out.

Copy link
Contributor

Choose a reason for hiding this comment

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

im not a babel/ast expert either :P so i dont know what best practices are

just had such an idea how it should be possible to address the issue from the comment

Copy link
Collaborator

Choose a reason for hiding this comment

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

I pushed another commit that contains some improvements for this logic.

@@ -47,3 +47,102 @@ describe("_blockHoist nodes", function() {
assert.deepEqual(names, ["hoistMe", "doNotHoistMe", "oyez"]);
});
});

context("functions", function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

are those good tests? I mean - they are highly coupled to the internal implementation, shouldnt we just test if the outcome is treeshakeable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'll add an uglify test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

Copy link
Contributor

Choose a reason for hiding this comment

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

👏 great work!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hah, thanks. is there anything else i can do to help expedite this merge? it makes a huge difference for me (and probably anyone else using redux-sagas).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll try to look at this today! Thanks so much to both of you for finding a simpler solution and perfecting it.

Copy link
Contributor

Choose a reason for hiding this comment

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

@modosc as maintainer of the redux-saga im a little bit surprised that this make u a huge difference. I thought this is more 'nice to have' and 'always better have this in place' rather than a game changer. Do u have any numbers for ur project? What will be tree shaken after this change?

I see when this might make a change when u have some shared saga utils module used between projects, but i think most dont

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Andarist lol, didn't realize that.

in our case we're generating api sagas automatically from swagger docs and grouping them by model. we've got at least one saga for every single version / endpoint / method so for some models we have >30 sagas in a single file. most of them aren't used but due to this issue using a single saga from a file pulls in all the others.

in retrospect i probably overstated the value of this to the average user - in normal circumstances i can't imagine having that many sagas sitting around that aren't actually used (although i can imagine situations with code-spitting where you end up bundling an entire module because you want access to a single saga inside of it).

Copy link
Contributor

Choose a reason for hiding this comment

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

in our case we're generating api sagas automatically from swagger docs and grouping them by model.

metaprogramming level hard :D would love to see this in action

although i can imagine situations with code-spitting where you end up bundling an entire module because you want access to a single saga inside of it

yeah, thats true, however i see the issue only for 'util' saga files, others are rather grouped by some kind of functionality and they rather work only together

@benjamn benjamn self-assigned this Aug 11, 2017
@benjamn
Copy link
Collaborator

benjamn commented Aug 15, 2017

Thanks @modosc and @Andarist for iterating on this and making it so much simpler!

Now to publish these packages with Lerna for the first time…

@benjamn
Copy link
Collaborator

benjamn commented Aug 15, 2017

You can see these changes live in the sandbox now! http://facebook.github.io/regenerator/

@Andarist
Copy link
Contributor

wohoooooo! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants