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

feat(gatsby): Move page component state & side effect handling to xstate #11897

Merged
merged 53 commits into from
Mar 26, 2019

Conversation

KyleAMathews
Copy link
Contributor

@KyleAMathews KyleAMathews commented Feb 19, 2019

Internally within Gatsby, some of our most complex logic is around creating and managing page components — there's quite a few states a page component can be in e.g. while bootstrapping, extracting queries, running queries for its pages, etc.

The code for handling this has been some of the most annoying and bug prone code to work in.

XState has been on list of things to try in core for quite a while as it's perfect for these sorts of things. @m-allanson did a small PR adding it for ludicrious mode last year #4555 and it's been problem free there.

Visualization

Screenshot 2019-03-22 18 49 42

TODOs

  • extract machine to separate directory
  • add tests for critical flows

Follow up questions/work

  • Model individual pages
  • how to model static queries? It looks like they're getting run every time their component changes.
  • would xstate be helpful for data-driven state changes? Presumably as a data change could happen at the same time as a query is being extracted. So we should then send action when a page is dirtied.
  • it seems like handling error reporting in machines would work really well
  • how do we want to make private actions truly private? Move to different file? Mark with underscore and filter out from passing to plugins?

@KyleAMathews KyleAMathews requested a review from a team as a code owner February 19, 2019 15:34
@KyleAMathews KyleAMathews changed the title Move component state/actions to xstate [WIP] Move component state/actions to xstate Feb 19, 2019
…while bootstrapping + initiate extracting queries from machine when new page component is created
@@ -55,21 +55,22 @@ function formatError(message: string, filePath: string, codeFrame: string) {
}

function extractError(error: Error): { message: string, docName: string } {
const docRegex = /Invariant Violation: (RelayParser|GraphQLParser): (.*). Source: document `(.*)` file:/g
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This regex broke when we moved to Relay Compiler v2.

* Report that a query has been extracted from a component. Used by
* query-compilier.js.
*
* @param {Object} $0
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 want to use actions more for internal communication but don't want them all to show up here (https://www.gatsbyjs.org/docs/actions/)

Thinking we could start marking actions as private to get around this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm all for using actions more (and less event emitting). Can we move them to the actions folder that already exists (but only has the page dependencies stuff), and export a subset that can be used in node APIs?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hah, that's pretty much what we talked about yesterday. One idea is to have public-actions and private-actions files. Other could be, have extra export here:

/**
* All action creators wrapped with a dispatch. - *DEPRECATED*
*/
exports.boundActionCreators = bindActionCreators(actions, store.dispatch)

const boundActionCreators = bindActionCreators(actions, store.dispatch)
exports.boundActionCreators = boundActionCreators
/**
 * This will be used in `api-runner-node.js`
 */
exports.publicBoundActionCreators = _.pick(boundActionCreators, [
  `createNode`,
  `createPage`,
  // and all other actions that should be public
])

But it's not in scope for this PR

Copy link
Contributor

Choose a reason for hiding this comment

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

I like just using pick :)

)

const services = new Map()
let programStatus = `BOOTSTRAPPING`
Copy link
Contributor

Choose a reason for hiding this comment

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

we probably want to move to an enum.

@@ -157,3 +157,7 @@ emitter.on(`DELETE_PAGE`, action => {
const node = getNode(nodeId)
boundActionCreators.deleteNode({ node })
})

emitter.on(`BOOTSTRAP_FINISHED`, () =>
boundActionCreators.setProgramStatus(`BOOTSTRAP_FINISHED`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that'd make more sense 👍

@@ -42,13 +42,14 @@ const runQueries = async () => {

// Find ids without data dependencies (i.e. no queries have been run for
// them before) and run them.
const cleanIds = findIdsWithoutDataDependencies()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably need to restore this? For now

@DSchau
Copy link
Contributor

DSchau commented Mar 13, 2019

@KyleAMathews @pieh didn't have as much time today as I would've liked, but got through some of these. Definitely a few issues thus far that need fixing.

I'll look into actually fixing these tomorrow afternoon.

Here's what I found thus far:

Issues

4.iv) Deleting a page seems to hard fail

  • Add a page (e.g. pages/page-2.js)
  • Delete the page
TypeError: expected a string

- index.js:12 normalizePath
[xstate-example]/[normalize-path]/index.js:12:11
  - components.js:131 module.exports
    [xstate-example]/[gatsby]/dist/redux/reducers/components.js:131:40
  
  - redux.js:451 combination
    [xstate-example]/[redux]/lib/redux.js:451:29
  
  - redux.js:211 dispatch
    [xstate-example]/[redux]/lib/redux.js:211:22
  
  - index.js:78 action
    [xstate-example]/[gatsby]/dist/redux/index.js:78:91
  
  - redux.js:468 
    [xstate-example]/[redux]/lib/redux.js:468:12
  
  - api-runner-node.js:55 doubleBoundActionCreators.(anonymous function).args
    [xstate-example]/[gatsby]/dist/utils/api-runner-node.js:55:13
  1. Recovering from an Error
    • Used throw new Error in a function body
    • Hot reload failed
    • Removed throw new error, error still appears

Misc. / Possibly Unrelated

  • StaticQuery cannot be changed and hot reloaded (or really used at all in the development server)

    Error: The result of this StaticQuery could not be fetched.

  • Hot reloading seems to log things at least twice?
    • This may be unrelated to this PR
    • Add a console.log in a page/template/component and it logs (at least) twice

@pieh
Copy link
Contributor

pieh commented Mar 15, 2019

4.iv) Deleting a page seems to hard fail

  • Add a page (e.g. pages/page-2.js)
  • Delete the page

Should be fixed in 36bda2e

  • StaticQuery cannot be changed and hot reloaded (or really used at all in the development server)

    Error: The result of this StaticQuery could not be fetched.

This is bigger one - with approach change in here with emitting PAGE_COMPONENT_CHANGE action and not calling to extract directly we essentially we will never trigger query runs for static queries changes (edits/adding new static queries or removing old ones). I'm not exactly sure why query extraction was moved to be called by page-component machine and why it needs to be tracked there. I will open PR against this branch so it can be reviewed separately

@KyleAMathews
Copy link
Contributor Author

Gotta another plane ride so I can work on the static query stuff.

…ant changes to static queries weren't being run
…l successfully extracts queries so we can know to leave babe error state
@KyleAMathews
Copy link
Contributor Author

Made good progress on the train. Fixed the static query bug (by eliminating tracking extraction in the page component machine as you suggested @pieh).

I filled out the test plan a bunch more and from my manual tests, everything is currently working 🎉

  1. Compile during bootstrap
    1. compile all queries during bootstrap
    2. No queries are compiled until the appropriate place in bootstrap
  2. Run queries during bootstrap
    1. Run all queries when there’s a clean cache
    2. Run all queries for pages that have changed since the previous run:
      1. changed data
      2. changed queries
      3. queries we don’t have any tracked data for (so unknown state)
    3. No queries are run until the appropriate place in the bootstrap
    4. No queries are re-run (unless data/query changes) after the initial point in the bootstrap (implying that we know for sure at that point all queries which need run)
    5. [not worked if page query isn’t run] Each page is tracked
    6. Static Queries are run when they change
  3. Compile during development
    1. Queries should be re-compiled whenever a JS file inside src/ changes
    2. Queries should be updated inside page component machine
  4. Run queries during development
    1. When the query changes
    2. When data relied on by a query changes
    3. When a new page added
      1. with an existing page template
      2. with a new page template
    4. When an existing page component is deleted and then re-added (the old page data dependencies should be deleted so adding the new page runs everything fresh)
    5. When the data a page depends on is deleted and re-added, the page is recreated correctly and its query run again
  5. Error states handling (how do we help people resolve errors/warnings)
    1. JS & GraphQL errors in a JS file block their page queries from running
    2. Once the error is resolved, if the query has changed, queries are run again. If it’s a JS error, queries aren’t re-run

@KyleAMathews
Copy link
Contributor Author

Checked and https://github.com/rexxars/gatsby-node-update-bugs are also fixed

@KyleAMathews KyleAMathews requested a review from a team as a code owner March 22, 2019 01:21
@jlengstorf
Copy link
Contributor

Niiiice. Let me know when you've got an alpha for this and I'll run my sites against it. Super stoked to see this!

@ChristopherBiscardi
Copy link
Contributor

same. Happy to run the mdx examples and such against it too.

@KyleAMathews
Copy link
Contributor Author

Sweet thanks! Release [email protected]

@jlengstorf
Copy link
Contributor

jlengstorf commented Mar 22, 2019

[removed] whatever is happening is not due to this alpha. Apparently something else broke in my setup. 😕

@Moocar
Copy link
Contributor

Moocar commented Mar 24, 2019

Looking forward to this one. I've been working on a follow up PR that eliminates some of the eventing and global state by making the query runner queue constructable. I'll create a PR once this is merged. https://github.com/gatsbyjs/gatsby/compare/xstate-components...Moocar:query-runner-refactor?expand=1

@KyleAMathews
Copy link
Contributor Author

Tests passed — manually tested this on a number of sites + my manual checklist above.

Merging and publishing.

I'll create an issue for turning the checklist into automated tests.

@KyleAMathews KyleAMathews merged commit 91086d4 into master Mar 26, 2019
@KyleAMathews KyleAMathews deleted the xstate-components branch March 26, 2019 01:42
@DSchau
Copy link
Contributor

DSchau commented Mar 26, 2019

@KyleAMathews 🎉 awesome news!

@KyleAMathews
Copy link
Contributor Author

Successfully published:
 - [email protected]

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

Successfully merging this pull request may close these issues.

8 participants