Gridsome 0.2.5コードリーディング

[Gridsome](
https://github.com/gridsome/gridsome)はVueでサイトを構築していくGatsbyJSライクな静的サイトジェネレーター。

関連記事: [GatsbyJSライクなVueの静的サイトジェネレーター Gridsomeを触ってみた - mottox2 blog](https://mottox2.com/posts/224/)

**注意**
`0.2.5`の段階のコードなので、大きく変わっている可能性あり

執筆時点のコードはここから見れます
https://github.com/gridsome/gridsome/tree/568207fbc413c1b9bd4ff1a671e063ba540b5001

### CLI部分

CLI単体では、createやhelpなどのプロジェクトに依存しないコマンドを定義している。
gridsomeのプロジェクトであればrequireでgridsomeのindex.jsを呼び出している。

```js:/packages/cli/bin/gridsome.js
#!/usr/bin/env node

program
  .version(require('../package').version)
  .usage('<command> [options]')

program
  .command('create <name> [starter]')
  .description('create a new website')
  .action((...args) => {
    return wrapCommand(create)(...args)
  })

try {
  const gridsomePath = resolveCwd.silent('gridsome')

  if (gridsomePath) {
    // eslint-disable-next-line
    require(gridsomePath)({ context, program })
  }
} catch (err) {
  console.log(err)
}

// show a warning if the command does not exist
program.arguments('<command>').action((command) => {
  console.log(chalk.red(`Unknown command ${chalk.bold(command)}`))
})
```

上のCLIでrequireされるファイル。developやbuildなどのコマンド定義を呼び出している。
ここからはdevelopやbuildなどの実行を行う。

```js:/gridsome/index.js
module.exports = ({ context, program }) => {
  program
    .command('develop')
    .description('start development server')
    .option('-p, --port <port>', 'use specified port (default: 8080)')
    .option('-h, --host <host>', 'use specified host (default: 0.0.0.0)')
    .action(args => {
      wrapCommand(require('./lib/develop'))(context, args)
    })

  program
    .command('build')
    .description('build site for production')
    .action(() => {
      wrapCommand(require('./lib/build'))(context, {})
    })
```

ビルド用のファイル。重要なのはcreateApp。この中でpluginを読み込んだり、ページの情報を読み込んでいたりする。

```js:/gridsome/lib/build.js
module.exports = async (context, args) => {
  process.env.NODE_ENV = 'production'
  process.env.GRIDSOME_MODE = 'static'

  const buildTime = hirestime()
  const app = await createApp(context, { args })
  const { config, graphql } = app

  await app.dispatch('beforeBuild', { context, config })
  await fs.ensureDir(config.cacheDir)
  await fs.remove(config.outDir)

  const queue = await createRenderQueue(app)

  // 1. run all GraphQL queries and save results into json files
  await app.dispatch('beforeRenderQueries', () => ({ context, config, queue }))
  await renderPageQueries(queue, graphql)

  // 2. compile assets with webpack
  await compileAssets(app)

  // 3. render a static index.html file for each possible route
  await app.dispatch('beforeRenderHTML', () => ({ context, config, queue }))
  await renderHTML(queue, config)

  // 4. process queued images
  await app.dispatch('beforeProcessAssets', () => ({ context, config, queue: app.queue }))
  await processFiles(app.queue.files, config)
  await processImages(app.queue.images, config)

  // 5. copy static files
  if (fs.existsSync(config.staticDir)) {
    await fs.copy(config.staticDir, config.targetDir)
  }

  // 6. clean up
  await app.dispatch('afterBuild', () => ({ context, config, queue }))
  await fs.remove(path.resolve(config.cacheDir, 'data'))
  await fs.remove(config.manifestsDir)

  console.log()
  console.log(`  Done in ${buildTime(hirestime.S)}s`)
  console.log()
}
```

new App()された後、bootstrapが呼ばれる。
bootstrap内でいろいろやっている。

```js:/gridsome/lib/app/App.js
class App {
  constructor (context, options) {
    process.GRIDSOME = this

    this.events = []
    this.clients = {}
    this.plugins = []
    this.context = context
    this.config = loadConfig(context, options)
    this.isInitialized = false
    this.isBootstrapped = false

    autoBind(this)
  }

  async bootstrap (phase) {
    const bootstrapTime = hirestime()

    const phases = [
      { title: 'Initialize', run: this.init },
      { title: 'Load sources', run: this.loadSources },
      { title: 'Create GraphQL schema', run: this.createSchema },
      { title: 'Generate code', run: this.generateFiles }
    ]

    console.log(`Gridsome v${version}`)
    console.log()

    for (const current of phases) {
      if (phases.indexOf(current) <= phase) {
        const timer = hirestime()
        await current.run(this)

        console.info(`${current.title} - ${timer(hirestime.S)}s`)
      }
    }

    console.info(`Bootstrap finish - ${bootstrapTime(hirestime.S)}s`)

    this.isBootstrapped = true

    return this
  }

  ...
}
```

入り口の重要な部分だけ取り上げた。各機能に興味があると次のようなファイルから見るといいかもしれない。

* 開発環境の処理に興味があれば gridsome/lib/dev.js
* Pluginに興味があれば gridsome/lib/app/App.js のinitの読み込み処理とgridsome/lib/app/PluginAPI.jsあたり
* GraphQLの処理しているデータは gridsome/lib/graphql/PluginStore.jsあたり。これらが各プラグインで呼ばれているイメージ
* vueファイル内のpage-queryのwebpackの設定は gridsome/lib/webpack/loaders/page-query.js