From 17a67a5909180da336879d5adcf4d6227d53c008 Mon Sep 17 00:00:00 2001 From: stefanprobst Date: Tue, 14 May 2019 21:40:31 +0200 Subject: [PATCH] feat(gatsby): allow awaiting API run triggered by createNode action (#12748) --- .../src/create-remote-file-node.js | 2 +- packages/gatsby/package.json | 1 + packages/gatsby/src/redux/__tests__/nodes.js | 51 ++++++++++--------- packages/gatsby/src/redux/actions.js | 25 ++++++++- packages/gatsby/src/redux/index.js | 47 ++++++++--------- packages/gatsby/src/redux/plugin-runner.js | 12 ----- packages/gatsby/src/utils/api-runner-node.js | 5 +- yarn.lock | 5 ++ 8 files changed, 86 insertions(+), 62 deletions(-) diff --git a/packages/gatsby-source-filesystem/src/create-remote-file-node.js b/packages/gatsby-source-filesystem/src/create-remote-file-node.js index 54d0dc8a12c10..4465ced0f0de4 100644 --- a/packages/gatsby-source-filesystem/src/create-remote-file-node.js +++ b/packages/gatsby-source-filesystem/src/create-remote-file-node.js @@ -242,7 +242,7 @@ async function processRemoteNode({ // be the owner of File nodes or there'll be conflicts if any other // File nodes are created through normal usages of // gatsby-source-filesystem. - createNode(fileNode, { name: `gatsby-source-filesystem` }) + await createNode(fileNode, { name: `gatsby-source-filesystem` }) return fileNode } diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 82c2366952971..194c83f76506a 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -109,6 +109,7 @@ "react-error-overlay": "^3.0.0", "react-hot-loader": "^4.6.2", "redux": "^4.0.0", + "redux-thunk": "^2.3.0", "semver": "^5.6.0", "shallow-compare": "^1.2.2", "sift": "^5.1.0", diff --git a/packages/gatsby/src/redux/__tests__/nodes.js b/packages/gatsby/src/redux/__tests__/nodes.js index f2954a7974678..2643be6ae3eba 100644 --- a/packages/gatsby/src/redux/__tests__/nodes.js +++ b/packages/gatsby/src/redux/__tests__/nodes.js @@ -1,4 +1,3 @@ -const Redux = require(`redux`) const { actions } = require(`../actions`) const nodeReducer = require(`../reducers/nodes`) const nodeTouchedReducer = require(`../reducers/nodes-touched`) @@ -6,19 +5,15 @@ const nodeTouchedReducer = require(`../reducers/nodes-touched`) jest.mock(`../../db/nodes`) jest.mock(`../nodes`) -const store = Redux.createStore( - Redux.combineReducers({ nodeReducer, nodeTouchedReducer }), - {} -) +const dispatch = jest.fn() + describe(`Create and update nodes`, () => { beforeEach(() => { - store.dispatch({ - type: `DELETE_CACHE`, - }) + dispatch.mockClear() }) it(`allows creating nodes`, () => { - const action = actions.createNode( + actions.createNode( { id: `hi`, children: [], @@ -32,13 +27,14 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) + )(dispatch) + const action = dispatch.mock.calls[0][0] expect(action).toMatchSnapshot() expect(nodeReducer(undefined, action)).toMatchSnapshot() }) it(`allows updating nodes`, () => { - const action = actions.createNode( + actions.createNode( { id: `hi`, children: [], @@ -61,8 +57,10 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) - const updateAction = actions.createNode( + )(dispatch) + const action = dispatch.mock.calls[0][0] + + actions.createNode( { id: `hi`, children: [], @@ -82,7 +80,9 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) + )(dispatch) + const updateAction = dispatch.mock.calls[1][0] + let state = nodeReducer(undefined, action) state = nodeReducer(state, updateAction) expect(state.get(`hi`).pickle).toEqual(false) @@ -91,7 +91,7 @@ describe(`Create and update nodes`, () => { }) it(`nodes that are added are also "touched"`, () => { - const action = actions.createNode( + actions.createNode( { id: `hi`, children: [], @@ -105,13 +105,15 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) + )(dispatch) + const action = dispatch.mock.calls[0][0] + let state = nodeTouchedReducer(undefined, action) expect(state[`hi`]).toBe(true) }) it(`allows adding fields to nodes`, () => { - const action = actions.createNode( + actions.createNode( { id: `hi`, children: [], @@ -125,7 +127,8 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) + )(dispatch) + const action = dispatch.mock.calls[0][0] let state = nodeReducer(undefined, action) const addFieldAction = actions.createNodeField( @@ -138,12 +141,13 @@ describe(`Create and update nodes`, () => { name: `test`, } ) + state = nodeReducer(state, addFieldAction) expect(state).toMatchSnapshot() }) it(`throws error if a field is updated by a plugin not its owner`, () => { - const action = actions.createNode( + actions.createNode( { id: `hi`, children: [], @@ -157,7 +161,8 @@ describe(`Create and update nodes`, () => { { name: `tests`, } - ) + )(dispatch) + const action = dispatch.mock.calls[0][0] let state = nodeReducer(undefined, action) const addFieldAction = actions.createNodeField( @@ -202,7 +207,7 @@ describe(`Create and update nodes`, () => { { name: `pluginA`, } - ) + )(dispatch) function callActionCreator() { actions.createNode( @@ -219,7 +224,7 @@ describe(`Create and update nodes`, () => { { name: `pluginB`, } - ) + )(dispatch) } expect(callActionCreator).toThrowErrorMatchingSnapshot() @@ -244,7 +249,7 @@ describe(`Create and update nodes`, () => { { name: `pluginA`, } - ) + )(dispatch) } expect(callActionCreator).toThrowErrorMatchingSnapshot() diff --git a/packages/gatsby/src/redux/actions.js b/packages/gatsby/src/redux/actions.js index 1c8c8382116d4..6849554793533 100644 --- a/packages/gatsby/src/redux/actions.js +++ b/packages/gatsby/src/redux/actions.js @@ -17,6 +17,7 @@ const { store } = require(`./index`) const fileExistsSync = require(`fs-exists-cached`).sync const joiSchemas = require(`../joi-schemas/joi`) const { generateComponentChunkName } = require(`../utils/js-chunk-names`) +const apiRunnerNode = require(`../utils/api-runner-node`) const actions = {} @@ -527,6 +528,8 @@ const typeOwners = {} * readable description of what this node represent / its source. It will * be displayed when type conflicts are found, making it easier to find * and correct type conflicts. + * @returns {Promise} The returned Promise resolves when all cascading + * `onCreateNode` API calls triggered by `createNode` have finished. * @example * createNode({ * // Data for the node. @@ -551,7 +554,7 @@ const typeOwners = {} * } * }) */ -actions.createNode = ( +const createNode = ( node: any, plugin?: Plugin, actionOptions?: ActionOptions = {} @@ -716,6 +719,26 @@ actions.createNode = ( } } +actions.createNode = (...args) => dispatch => { + const actions = createNode(...args) + dispatch(actions) + const createNodeAction = (Array.isArray(actions) ? actions : [actions]).find( + action => action.type === `CREATE_NODE` + ) + + if (!createNodeAction) { + return undefined + } + + const { payload: node, traceId, parentSpan } = createNodeAction + return apiRunnerNode(`onCreateNode`, { + node, + traceId, + parentSpan, + traceTags: { nodeId: node.id, nodeType: node.internal.type }, + }) +} + /** * "Touch" a node. Tells Gatsby a node still exists and shouldn't * be garbage collected. Primarily useful for source plugins fetching diff --git a/packages/gatsby/src/redux/index.js b/packages/gatsby/src/redux/index.js index 9f598a0171ef3..2ff0362f22151 100644 --- a/packages/gatsby/src/redux/index.js +++ b/packages/gatsby/src/redux/index.js @@ -2,14 +2,13 @@ const Redux = require(`redux`) const _ = require(`lodash`) const mitt = require(`mitt`) +const thunk = require(`redux-thunk`).default +const reducers = require(`./reducers`) +const { writeToCache, readFromCache } = require(`./persist`) // Create event emitter for actions const emitter = mitt() -// Reducers -const reducers = require(`./reducers`) -const { writeToCache, readFromCache } = require(`./persist`) - // Read old node data from cache. const readState = () => { try { @@ -32,21 +31,23 @@ const readState = () => { return {} } -exports.readState = readState +/** + * Redux middleware handling array of actions + */ +const multi = ({ dispatch }) => next => action => + Array.isArray(action) ? action.filter(Boolean).map(dispatch) : next(action) -const store = Redux.createStore( - Redux.combineReducers({ ...reducers }), - readState(), - Redux.applyMiddleware(function multi({ dispatch }) { - return next => action => - Array.isArray(action) - ? action.filter(Boolean).map(dispatch) - : next(action) - }) -) +const configureStore = initialState => + Redux.createStore( + Redux.combineReducers({ ...reducers }), + initialState, + Redux.applyMiddleware(thunk, multi) + ) + +const store = configureStore(readState()) // Persist state. -function saveState() { +const saveState = () => { if (process.env.DANGEROUSLY_DISABLE_OOM) { return Promise.resolve() } @@ -64,15 +65,15 @@ function saveState() { return writeToCache(pickedState) } -exports.saveState = saveState - store.subscribe(() => { const lastAction = store.getState().lastAction emitter.emit(lastAction.type, lastAction) }) -/** Event emitter */ -exports.emitter = emitter - -/** Redux store */ -exports.store = store +module.exports = { + emitter, + store, + configureStore, + readState, + saveState, +} diff --git a/packages/gatsby/src/redux/plugin-runner.js b/packages/gatsby/src/redux/plugin-runner.js index fb19f43cf3448..bbc585226dec1 100644 --- a/packages/gatsby/src/redux/plugin-runner.js +++ b/packages/gatsby/src/redux/plugin-runner.js @@ -1,20 +1,8 @@ // Invoke plugins for certain actions. const { emitter } = require(`./index`) -const { getNode } = require(`../db/nodes`) const apiRunnerNode = require(`../utils/api-runner-node`) -emitter.on(`CREATE_NODE`, action => { - const node = getNode(action.payload.id) - const traceTags = { nodeId: node.id, nodeType: node.internal.type } - apiRunnerNode(`onCreateNode`, { - node, - traceId: action.traceId, - parentSpan: action.parentSpan, - traceTags, - }) -}) - emitter.on(`CREATE_PAGE`, action => { const page = action.payload apiRunnerNode( diff --git a/packages/gatsby/src/utils/api-runner-node.js b/packages/gatsby/src/utils/api-runner-node.js index 527ff7b28716a..f5b7dc0154dd8 100644 --- a/packages/gatsby/src/utils/api-runner-node.js +++ b/packages/gatsby/src/utils/api-runner-node.js @@ -37,10 +37,11 @@ const doubleBind = (boundActionCreators, api, plugin, actionOptions) => { // Let action callers override who the plugin is. Shouldn't be // used that often. if (args.length === 1) { - boundActionCreator(args[0], plugin, actionOptions) + return boundActionCreator(args[0], plugin, actionOptions) } else if (args.length === 2) { - boundActionCreator(args[0], args[1], actionOptions) + return boundActionCreator(args[0], args[1], actionOptions) } + return undefined } } } diff --git a/yarn.lock b/yarn.lock index c49107757d05c..80e65a745550b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17742,6 +17742,11 @@ reduce@^1.0.1: dependencies: object-keys "~1.0.0" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + redux@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"