mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-01-13 19:01:25 -06:00
* fix(cors): only allow localhost * fix: use host so it's configurable * fix: use cors options object * feat: use a custom graphql-server instead of the one from apollo plugin exports the httpServer instance * fix: add CORS validation in the http upgrade request Co-authored-by: Haoqun Jiang <haoqunjiang@gmail.com>
220 lines
5.9 KiB
JavaScript
220 lines
5.9 KiB
JavaScript
// modified from vue-cli-plugin-apollo/graphql-server
|
|
// added a return value for the server() call
|
|
|
|
const http = require('http')
|
|
const { chalk } = require('@vue/cli-shared-utils')
|
|
const express = require('express')
|
|
const { ApolloServer, gql } = require('apollo-server-express')
|
|
const { PubSub } = require('graphql-subscriptions')
|
|
const merge = require('deepmerge')
|
|
|
|
function defaultValue (provided, value) {
|
|
return provided == null ? value : provided
|
|
}
|
|
|
|
function autoCall (fn, ...context) {
|
|
if (typeof fn === 'function') {
|
|
return fn(...context)
|
|
}
|
|
return fn
|
|
}
|
|
|
|
module.exports = (options, cb = null) => {
|
|
// Default options
|
|
options = merge({
|
|
integratedEngine: false
|
|
}, options)
|
|
|
|
// Express app
|
|
const app = express()
|
|
|
|
// Customize those files
|
|
let typeDefs = load(options.paths.typeDefs)
|
|
const resolvers = load(options.paths.resolvers)
|
|
const context = load(options.paths.context)
|
|
const schemaDirectives = load(options.paths.directives)
|
|
let pubsub
|
|
try {
|
|
pubsub = load(options.paths.pubsub)
|
|
} catch (e) {
|
|
if (process.env.NODE_ENV !== 'production' && !options.quiet) {
|
|
console.log(chalk.yellow('Using default PubSub implementation for subscriptions.'))
|
|
console.log(chalk.grey('You should provide a different implementation in production (for example with Redis) by exporting it in \'apollo-server/pubsub.js\'.'))
|
|
}
|
|
}
|
|
let dataSources
|
|
try {
|
|
dataSources = load(options.paths.dataSources)
|
|
} catch (e) {}
|
|
|
|
// GraphQL API Server
|
|
|
|
// Realtime subscriptions
|
|
if (!pubsub) pubsub = new PubSub()
|
|
|
|
// Customize server
|
|
try {
|
|
const serverModule = load(options.paths.server)
|
|
serverModule(app)
|
|
} catch (e) {
|
|
// No file found
|
|
}
|
|
|
|
// Apollo server options
|
|
|
|
typeDefs = processSchema(typeDefs)
|
|
|
|
let apolloServerOptions = {
|
|
typeDefs,
|
|
resolvers,
|
|
schemaDirectives,
|
|
dataSources,
|
|
tracing: true,
|
|
cacheControl: true,
|
|
engine: !options.integratedEngine,
|
|
// Resolvers context from POST
|
|
context: async ({ req, connection }) => {
|
|
let contextData
|
|
try {
|
|
if (connection) {
|
|
contextData = await autoCall(context, { connection })
|
|
} else {
|
|
contextData = await autoCall(context, { req })
|
|
}
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
contextData = Object.assign({}, contextData, { pubsub })
|
|
return contextData
|
|
},
|
|
// Resolvers context from WebSocket
|
|
subscriptions: {
|
|
path: options.subscriptionsPath,
|
|
onConnect: async (connection, websocket) => {
|
|
let contextData = {}
|
|
try {
|
|
contextData = await autoCall(context, {
|
|
connection,
|
|
websocket
|
|
})
|
|
contextData = Object.assign({}, contextData, { pubsub })
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
return contextData
|
|
}
|
|
}
|
|
}
|
|
|
|
// Automatic mocking
|
|
if (options.enableMocks) {
|
|
// Customize this file
|
|
apolloServerOptions.mocks = load(options.paths.mocks)
|
|
apolloServerOptions.mockEntireSchema = false
|
|
|
|
if (!options.quiet) {
|
|
if (process.env.NODE_ENV === 'production') {
|
|
console.warn('Automatic mocking is enabled, consider disabling it with the \'enableMocks\' option.')
|
|
} else {
|
|
console.log('✔️ Automatic mocking is enabled')
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apollo Engine
|
|
if (options.enableEngine && options.integratedEngine) {
|
|
if (options.engineKey) {
|
|
apolloServerOptions.engine = {
|
|
apiKey: options.engineKey,
|
|
schemaTag: options.schemaTag,
|
|
...options.engineOptions || {}
|
|
}
|
|
console.log('✔️ Apollo Engine is enabled')
|
|
} else if (!options.quiet) {
|
|
console.log(chalk.yellow('Apollo Engine key not found.') + `To enable Engine, set the ${chalk.cyan('VUE_APP_APOLLO_ENGINE_KEY')} env variable.`)
|
|
console.log('Create a key at https://engine.apollographql.com/')
|
|
console.log('You may see `Error: Must provide document` errors (query persisting tries).')
|
|
}
|
|
} else {
|
|
apolloServerOptions.engine = false
|
|
}
|
|
|
|
// Final options
|
|
apolloServerOptions = merge(apolloServerOptions, defaultValue(options.serverOptions, {}))
|
|
|
|
// Apollo Server
|
|
const server = new ApolloServer(apolloServerOptions)
|
|
|
|
// Express middleware
|
|
server.applyMiddleware({
|
|
app,
|
|
path: options.graphqlPath,
|
|
cors: options.cors
|
|
// gui: {
|
|
// endpoint: graphqlPath,
|
|
// subscriptionEndpoint: graphqlSubscriptionsPath,
|
|
// },
|
|
})
|
|
|
|
// Start server
|
|
const httpServer = http.createServer(app)
|
|
httpServer.setTimeout(options.timeout)
|
|
server.installSubscriptionHandlers(httpServer)
|
|
|
|
httpServer.listen({
|
|
host: options.host || 'localhost',
|
|
port: options.port
|
|
}, () => {
|
|
if (!options.quiet) {
|
|
console.log(`✔️ GraphQL Server is running on ${chalk.cyan(`http://localhost:${options.port}${options.graphqlPath}`)}`)
|
|
if (process.env.NODE_ENV !== 'production' && !process.env.VUE_CLI_API_MODE) {
|
|
console.log(`✔️ Type ${chalk.cyan('rs')} to restart the server`)
|
|
}
|
|
}
|
|
|
|
cb && cb()
|
|
})
|
|
|
|
// added in order to let vue cli to deal with the http upgrade request
|
|
return {
|
|
apolloServer: server,
|
|
httpServer
|
|
}
|
|
}
|
|
|
|
function load (file) {
|
|
const module = require(file)
|
|
if (module.default) {
|
|
return module.default
|
|
}
|
|
return module
|
|
}
|
|
|
|
function processSchema (typeDefs) {
|
|
if (Array.isArray(typeDefs)) {
|
|
return typeDefs.map(processSchema)
|
|
}
|
|
|
|
if (typeof typeDefs === 'string') {
|
|
// Convert schema to AST
|
|
typeDefs = gql(typeDefs)
|
|
}
|
|
|
|
// Remove upload scalar (it's already included in Apollo Server)
|
|
removeFromSchema(typeDefs, 'ScalarTypeDefinition', 'Upload')
|
|
|
|
return typeDefs
|
|
}
|
|
|
|
function removeFromSchema (document, kind, name) {
|
|
const definitions = document.definitions
|
|
const index = definitions.findIndex(
|
|
def => def.kind === kind && def.name.kind === 'Name' && def.name.value === name
|
|
)
|
|
if (index !== -1) {
|
|
definitions.splice(index, 1)
|
|
}
|
|
}
|