vue-cli
提供了可视化工具,也就是使用vue ui
所启动的cli-ui服务。它实际上是个node
服务,且提供了一个使用vue
开发的spa
页面,收到前端发送的请求时执行一些命令或读取一些文件。而这个node
服务使用了apollo-server
和express
。前后端通讯全部依靠graphql进行。
下面,从入口开始分析一下该node
服务的基本结构。
该文章默认已经熟悉了apollo-server
相关的概念及使用方法。
首先看一下服务是如何启动的:
在运行 vue ui
命令式会执行packages/@vue/cli/lib/ui中
默认导出的函数
module.exports = (...args) => {
return ui(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
其实也就是执行了上面定义的ui
函数:
const { portfinder, server } = require('@vue/cli-ui/server')
async function ui (options = {}, context = process.cwd()) {
const host = options.host || 'localhost'
let port = options.port
// 容错处理及环境变量
// ...
const opts = {
host,
port,
graphqlPath: '/graphql',
subscriptionsPath: '/graphql',
enableMocks: false,
enableEngine: false,
cors: {
origin: host
},
timeout: 1000000,
quiet: true,
paths: {
typeDefs: require.resolve('@vue/cli-ui/apollo-server/type-defs.js'),
resolvers: require.resolve('@vue/cli-ui/apollo-server/resolvers.js'),
context: require.resolve('@vue/cli-ui/apollo-server/context.js'),
pubsub: require.resolve('@vue/cli-ui/apollo-server/pubsub.js'),
server: require.resolve('@vue/cli-ui/apollo-server/server.js'),
directives: require.resolve('@vue/cli-ui/apollo-server/directives.js')
}
}
const { httpServer } = server(opts, () => {
// ...
const url = `http://${host}:${port}`
if (options.headless) {
console.log(port)
} else {
openBrowser(url)
}
})
httpServer.on('upgrade', simpleCorsValidation(host))
}
里面创建了一些选项,然后传入server函数执行。
这些选项是直接中关键的就是paths。里边获取了'@vue/cli-ui/apollo-server
中的几个文件。在这些文件里定义了apollo-server所需要的typeDefs、resolvers等。
而server函数是在'@vue/cli-ui/server
中导出的'@vue/cli-ui/graphql-server
。
首先看下入口文件,也就是根目录下的graphql-server.js
module.exports = (options, cb = null) => {
// ....
cosnt app = express(); //创建expess app
// 加载类型定义,解析器、context、订阅发布对象等
let typeDefs = load(options.paths.typeDefs)
const resolvers = load(options.paths.resolvers)
const context = load(options.paths.context)
// ...
let apolloServerOptions = { /* apollo server 选项 */ }
const server = new ApolloServer(apolloServerOptions);
// ...
// 创建服务器,监听端口...
可以看到,服务器入口很简单:
- 创建app实例
- 加载
apollo-server
所需选项 - 创建
apollo-server
实例 - 创建
http
服务,监听端口
这样就已经启动了node
服务。
回到上面所说的apollo-server
的选项,以加载typeDefs
为例:
let typeDefs = load(options.paths.typeDefs);
load
函数实际上就是使用require
函数将js引入。也就是引入了:
paths {
typeDefs: require.resolve('@vue/cli-ui/apollo-server/type-defs.js'),
// ...
}
也就是把这个文件通过require加载了进来。
// @vue/cli-ui/apollo-server/type-defs.js
const typeDefs = [gql`
scalar JSON
enum PackageManager {
npm
yarn
pnpm
}
// ...
`
]
const paths = globby.sync(['./schema/*.js'], { cwd: __dirname, absolute: true })
paths.forEach(file => {
const { types } = require(file)
types && typeDefs.push(types)
})
module.exports = typeDefs
可以看到,这里除了默认的gql类型定义之外,还从遍历了schema
文件夹内的所有js文件,将其导出的types
也加入typeDefs
中。
其余resolvers
等的加载也是同样的方法。
这样我们在入口处向apollo-server
构造函数传入的就是汇总后的所有typeDefs
和resolvers
等。
剩下就是对具体的请求的处理方式了。也是cli-ui的关键部分。
当客户端发起一个Query
、Mutation
或其他类型的查询时,会进入对应的同名的resolver
。resolver
做实际的处理,并返回数据。
如下所示:
const projects = require('../connectors/projects')
exports.types = gql`
extend type Query {
projects: [Project]
}
// ...
`
exports.resolvers = {
Query: {
projects: (root, args, context) => projects.list(context),
// 其他Query类型的resolver
},
}
这里的代码里为了简洁,将所有的处理逻辑放入了apollo-server/connectors
文件夹,在resolver
中只需要调用对应导出对象上的方法即可。
[未完待续(下一篇:cli-ui插件化机制实现原理)]