Created
June 5, 2019 09:39
-
-
Save IvanLieckens/295311730d2f8d760354272090a6fae7 to your computer and use it in GitHub Desktop.
JSS Disconnected mode GraphQL (Integrated + Connected) - Proof of Concept
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
When the app runs in disconnected mode, and Sitecore is not present, we need to give | |
the app copies of the Sitecore APIs it depends on (layout service, dictionary service, content service) | |
to talk to so that the app can run using the locally defined disconnected data. | |
This is accomplished by spinning up a small Express server that mocks the APIs, and then | |
telling the dev server to proxy requests to the API paths to this express instance. | |
*/ | |
// these environment variables are necessary for Vue to allow us | |
// to process transpiled ES6 that Node can run | |
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true; | |
process.env.VUE_CLI_BABEL_TARGET_NODE = true; | |
const fs = require('fs'); | |
const path = require('path'); | |
const { createDefaultDisconnectedServer } = require('@sitecore-jss/sitecore-jss-dev-tools'); | |
const config = require('../package.json').config; | |
const { createDisconnectedGraphqlService } = require('./graphql-service'); | |
const express = require('express'); | |
const introspectionResult = require('../data/graphql/schema.json').data; | |
const touchToReloadFilePath = 'src/temp/config.js'; | |
const proxyOptions = { | |
appRoot: path.join(__dirname, '..'), | |
appName: config.appName, | |
watchPaths: ['./data'], | |
language: config.language, | |
port: process.env.PROXY_PORT || 3042, | |
compilers: ['@babel/register'], | |
onManifestUpdated: (manifest) => { | |
// if we can resolve the config file, we can alter it to force reloading the app automatically | |
// instead of waiting for a manual reload. We must materially alter the _contents_ of the file to trigger | |
// an actual reload, so we append "// reloadnow" to the file each time. This will not cause a problem, | |
// since every build regenerates the config file from scratch and it's ignored from source control. | |
if (fs.existsSync(touchToReloadFilePath)) { | |
const currentFileContents = fs.readFileSync(touchToReloadFilePath, 'utf8'); | |
const newFileContents = `${currentFileContents}\n// reloadnow`; | |
fs.writeFileSync(touchToReloadFilePath, newFileContents, 'utf8'); | |
console.log('Manifest data updated. Reloading the browser.'); | |
} else { | |
console.log('Manifest data updated. Refresh the browser to see latest content!'); | |
} | |
}, | |
customizeRendering: (rendering, rawManifestRendering) => { | |
// Integrated graphQL adjustment for offline mode | |
let graphQLDataFilePath = path.resolve( | |
__dirname, | |
'./../data/graphql/Components/' + rawManifestRendering.renderingName + '.json' | |
); | |
if (fs.existsSync(graphQLDataFilePath)) { | |
let graphQLData = JSON.parse(fs.readFileSync(graphQLDataFilePath, 'utf8')); | |
rendering.fields.data = graphQLData; | |
return rendering; | |
} | |
return undefined; | |
}, | |
afterMiddlewareRegistered: (app) => { | |
// Connected graphQL adjustment for offline mode | |
app.use(express.json()); | |
app.use(express.urlencoded({ extended: true })); | |
let options = { | |
introspectionResult: introspectionResult, | |
mocks: { | |
JSON: () => { | |
return { value: 'Test' }; // This needs deeper research to properly output the correct value | |
}, | |
}, | |
}; | |
let graphqlService = createDisconnectedGraphqlService(options); | |
app.use(config.graphQLEndpointPath, graphqlService.middleware); | |
}, | |
}; | |
// Need to customize something that the proxy options don't support? | |
// createDefaultDisconnectedServer() is a boilerplate that you can copy from | |
// and customize the middleware registrations within as you see fit. | |
// See https://github.com/Sitecore/jss/blob/master/packages/sitecore-jss-dev-tools/src/disconnected-server/create-default-disconnected-server.ts | |
createDefaultDisconnectedServer(proxyOptions); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs'); | |
const path = require('path'); | |
const { graphql, buildClientSchema } = require('graphql'); | |
const { addMockFunctionsToSchema } = require('graphql-tools'); | |
exports.createDisconnectedGraphqlService = function(options) { | |
let schema = buildClientSchema(options.introspectionResult); | |
addMockFunctionsToSchema({ | |
schema, | |
mocks: options.mocks, | |
}); | |
return { | |
middleware: async function disconnectedGraphqlServiceMiddleware(request, response) { | |
let operationName = | |
request.body && request.body.length > 0 ? request.body[0].operationName : null; | |
let query = request.body && request.body.length > 0 ? request.body[0].query : null; | |
if (operationName) { | |
let operationResultPath = path.resolve( | |
__dirname, | |
'./../data/graphql/Operations/' + operationName + '.json' | |
); | |
response.json(JSON.parse(fs.readFileSync(operationResultPath, 'utf8'))); | |
return; | |
} else if (query) { | |
return graphql(schema, query).then((result) => response.json(result)); | |
} else { | |
response.json([{ errors: [{ message: 'PersistedQueryNotFound' }] }]); | |
return; | |
} | |
}, | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
query IntrospectionQuery { | |
__schema { | |
queryType { name } | |
mutationType { name } | |
subscriptionType { name } | |
types { | |
...FullType | |
} | |
directives { | |
name | |
locations | |
args { | |
...InputValue | |
} | |
} | |
} | |
} | |
fragment FullType on __Type { | |
kind | |
name | |
fields(includeDeprecated: true) { | |
name | |
args { | |
...InputValue | |
} | |
type { | |
...TypeRef | |
} | |
isDeprecated | |
deprecationReason | |
} | |
inputFields { | |
...InputValue | |
} | |
interfaces { | |
...TypeRef | |
} | |
enumValues(includeDeprecated: true) { | |
name | |
isDeprecated | |
deprecationReason | |
} | |
possibleTypes { | |
...TypeRef | |
} | |
} | |
fragment InputValue on __InputValue { | |
name | |
type { ...TypeRef } | |
defaultValue | |
} | |
fragment TypeRef on __Type { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
ofType { | |
kind | |
name | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment