Code Study: GitHub Desktop / ui / index.tsx

This is the entry point of renderer.js.

1. Install some globally available values for dev mode

if (__DEV__) {
  installDevGlobals()
}

/** Install some globally available values for dev mode. */
export function installDevGlobals() {
  const g: any = global
  // Expose GitPerf as a global so it can be started.
  g.GitPerf = require('./lib/git-perf')
}

2. Patch the current process to get important environment variable if needed

This is only applied to macOS installations due to how the application is launched. The code inspects whether the current process needs to be patched to get important environment variables for Desktop to work and integrate with other tools the user may invoke as part of their workflow. If patching is needed, it update the current process’s environment variables using environment variables from the user’s shell, if they can be retrieved successfully.

if (shellNeedsPatching(process)) {
  updateEnvironmentForProcess()
}

3. Enable source maps by calling enableSourceMaps

It uses the source-map-support npm module.

4. Tell dugite where to find the git environment

Dugite is an open source project from GitHub that provides bindings for Node applications to interact with Git repositories, using the same command line interface that core Git offers.

GitHub Desktop redistributes git.exe with itself so that end users do not have to manually install git before being able to use GitHub Desktop.

Git in GitHub Desktop

Here we tell Dugite where to find the local git command by setting an environment variable.

process.env['LOCAL_GIT_DIRECTORY'] = Path.resolve(__dirname, 'git')

5. Compile sass into css and inject it into the DOM

desktop.scss is the entry point for all style sheets of the app.

if (!process.env.TEST_ENV) {
  /* This is the magic trigger for webpack to go compile
  * our sass into css and inject it into the DOM. */
  require('../../styles/desktop.scss')
}

6. Register unhandled exception handler

When uncaught exception happens from inside the current renderer process, it will first make a copy of the error with a source-mapped stack trace. If it couldn’t perform the source mapping, it’ll use the original error stack.

Next, if we are in the dev or test environment, the app reports the error directly in the developer console window; otherwise, it informs the main process to report the error to GitHub’s error reporting server.

Finally, the renderer process sends a uncaught-exception IPC message to the main process so that the main process also has a chance to capture and handle it.

process.once('uncaughtException', (error: Error) => {
  error = withSourceMappedStack(error)

  console.error('Uncaught exception', error)

  if (__DEV__ || process.env.TEST_ENV) {
    console.error(
      `An uncaught exception was thrown. If this were a production build it would be reported to Central. Instead, maybe give it a lil lookyloo.`
    )
  } else {
    sendErrorReport(error, {
      osVersion: getOS(),
      guid: getGUID(),
    })
  }

  reportUncaughtException(error)
})

7. Initialize the AppStore

const appStore = new AppStore(
  gitHubUserStore,
  cloningRepositoriesStore,
  issuesStore,
  statsStore,
  signInStore,
  accountsStore,
  repositoriesStore,
  pullRequestStore,
  repositoryStateManager
)

8. Initialize the Dispatcher

Initialize the dispatcher, and register error handlers for different situations.

const dispatcher = new Dispatcher(appStore, repositoryStateManager, statsStore)

dispatcher.registerErrorHandler(defaultErrorHandler)
dispatcher.registerErrorHandler(upstreamAlreadyExistsHandler)
dispatcher.registerErrorHandler(externalEditorErrorHandler)
dispatcher.registerErrorHandler(openShellErrorHandler)
dispatcher.registerErrorHandler(mergeConflictHandler)
dispatcher.registerErrorHandler(lfsAttributeMismatchHandler)
dispatcher.registerErrorHandler(gitAuthenticationErrorHandler)
dispatcher.registerErrorHandler(pushNeedsPullHandler)
dispatcher.registerErrorHandler(backgroundTaskHandler)
dispatcher.registerErrorHandler(missingRepositoryHandler)

9. Add platform name to the html body class

document.body.classList.add(`platform-${process.platform}`)

10. Handle the ‘focus’ event

ipcRenderer.on('focus', () => {
  const { selectedState } = appStore.getState()

  // Refresh the currently selected repository on focus (if
  // we have a selected repository).
  if (selectedState && selectedState.type === SelectionType.Repository) {
    dispatcher.refreshRepository(selectedState.repository)
  }

  dispatcher.setAppFocusState(true)
})

11. Handle the ‘blur’ event

ipcRenderer.on('blur', () => {
  // Make sure we stop highlighting the menu button (on non-macOS)
  // when someone uses Alt+Tab to switch application since we won't
  // get the onKeyUp event for the Alt key in that case.
  dispatcher.setAccessKeyHighlightState(false)
  dispatcher.setAppFocusState(false)
})

12. Handle the ‘url-action’ event

See How URL Action Works in Github Desktop

ipcRenderer.on(
  'url-action',
  (event: Electron.IpcMessageEvent, { action }: { action: URLActionType }) => {
    dispatcher.dispatchURLAction(action)
  }
)

13. Render the App component

Render the App component in the ‘desktop-app-container’ element of index.html. ‘dispatch’, ‘appStore’, ‘repositoryStateManager’, ‘issuesStore’, ‘gitHubUserStore’, and ‘startTime’ are passed to the App component as props (IAppProps).

ReactDOM.render(
  <App
    dispatcher={dispatcher}
    appStore={appStore}
    repositoryStateManager={repositoryStateManager}
    issuesStore={issuesStore}
    gitHubUserStore={gitHubUserStore}
    startTime={startTime}
  />,
  document.getElementById('desktop-app-container')!
)
tags: GitHub Desktop - Electron - React