Skip to content

Instantly share code, notes, and snippets.

@IvanLieckens
Created June 5, 2019 09:39
Show Gist options
  • Save IvanLieckens/295311730d2f8d760354272090a6fae7 to your computer and use it in GitHub Desktop.
Save IvanLieckens/295311730d2f8d760354272090a6fae7 to your computer and use it in GitHub Desktop.
JSS Disconnected mode GraphQL (Integrated + Connected) - Proof of Concept
/*
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);
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;
}
},
};
};
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