Skip to content

Commit

Permalink
chore(gatsby) Rewrite requires-writer.js to TypeScript (#24289)
Browse files Browse the repository at this point in the history
- Mostly just adds TypeScript type annotations

- Introduces one assertion for matchPath !== undefined
as IGatsbyPage has it as string | undefined, but everything
in requires-writer.js assumes that it's a string. It would throw an
error anyway, I just made a bit more pretty with reporter.panic()

WARNING:
- requires-writer uses reporter from gatsby-cli, and not the
TS source, but the compiled version. Not sure if it's ok.
Maybe we should extract reporter into a separate package if
it is used in both gatsby and gasby-cli
  • Loading branch information
alexpyzhianov authored May 26, 2020
1 parent 5224347 commit 08709a2
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 49 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby/src/bootstrap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ process.on(`unhandledRejection`, (reason, p) => {

import { createGraphQLRunner } from "./create-graphql-runner"
const { extractQueries } = require(`../query/query-watcher`)
const requiresWriter = require(`./requires-writer`)
import * as requiresWriter from "./requires-writer"
import { writeRedirects, startRedirectListener } from "./redirects-writer"

// Override console.log to add the source file + line number.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
const _ = require(`lodash`)
const path = require(`path`)
const fs = require(`fs-extra`)
const crypto = require(`crypto`)
const { slash } = require(`gatsby-core-utils`)
const { store, emitter } = require(`../redux/`)
const reporter = require(`gatsby-cli/lib/reporter`)
const { match } = require(`@reach/router/lib/utils`)
import _ from "lodash"
import path from "path"
import fs from "fs-extra"
import crypto from "crypto"
import { slash } from "gatsby-core-utils"
import reporter from "gatsby-cli/lib/reporter"
import { match } from "@reach/router/lib/utils"
import { joinPath } from "gatsby-core-utils"
import { store, emitter } from "../redux/"
import { IGatsbyState, IGatsbyPage } from "../redux/types"

interface IGatsbyPageComponent {
component: string
componentChunkName: string
}

interface IGatsbyPageMatchPath {
path: string
matchPath: string | undefined
}

// path ranking algorithm copied (with small adjustments) from `@reach/router` (internal util, not exported from the package)
// https://github.com/reach/router/blob/28a79e7fc3a3487cb3304210dc3501efb8a50eba/src/lib/utils.js#L216-L254
Expand All @@ -18,11 +29,17 @@ const DYNAMIC_POINTS = 2
const SPLAT_PENALTY = 1
const ROOT_POINTS = 1

const isRootSegment = segment => segment === ``
const isDynamic = segment => paramRe.test(segment)
const isSplat = segment => segment === `*`
const isRootSegment = (segment: string): boolean => segment === ``
const isDynamic = (segment: string): boolean => paramRe.test(segment)
const isSplat = (segment: string): boolean => segment === `*`

const segmentize = (uri: string): string[] =>
uri
// strip starting/ending slashes
.replace(/(^\/+|\/+$)/g, ``)
.split(`/`)

const rankRoute = path =>
const rankRoute = (path: string): number =>
segmentize(path).reduce((score, segment) => {
score += SEGMENT_POINTS
if (isRootSegment(segment)) score += ROOT_POINTS
Expand All @@ -31,24 +48,18 @@ const rankRoute = path =>
else score += STATIC_POINTS
return score
}, 0)

const segmentize = uri =>
uri
// strip starting/ending slashes
.replace(/(^\/+|\/+$)/g, ``)
.split(`/`)
// end of copied `@reach/router` internals

let lastHash = null
let lastHash: string | null = null

const resetLastHash = () => {
export const resetLastHash = (): void => {
lastHash = null
}

const pickComponentFields = page =>
const pickComponentFields = (page: IGatsbyPage): IGatsbyPageComponent =>
_.pick(page, [`component`, `componentChunkName`])

const getComponents = pages =>
export const getComponents = (pages: IGatsbyPage[]): IGatsbyPageComponent[] =>
_(pages)
.map(pickComponentFields)
.uniqBy(c => c.componentChunkName)
Expand All @@ -59,17 +70,36 @@ const getComponents = pages =>
* Get all dynamic routes and sort them by most specific at the top
* code is based on @reach/router match utility (https://github.com/reach/router/blob/152aff2352bc62cefc932e1b536de9efde6b64a5/src/lib/utils.js#L224-L254)
*/
const getMatchPaths = pages => {
const createMatchPathEntry = (page, index) => {
const getMatchPaths = (pages: IGatsbyPage[]): IGatsbyPageMatchPath[] => {
interface IMatchPathEntry extends IGatsbyPage {
index: number
score: number
matchPath: string
}

const createMatchPathEntry = (
page: IGatsbyPage,
index: number
): IMatchPathEntry => {
const { matchPath } = page

if (matchPath === undefined) {
return reporter.panic(
`Error: matchPath property is undefined for page ${page.path}, should be a string`
) as never
}

return {
...page,
matchPath,
index,
score: rankRoute(page.matchPath),
score: rankRoute(matchPath),
}
}

const matchPathPages = []
pages.forEach((page, index) => {
const matchPathPages: IMatchPathEntry[] = []

pages.forEach((page: IGatsbyPage, index: number): void => {
if (page.matchPath) {
matchPathPages.push(createMatchPathEntry(page, index))
}
Expand All @@ -81,8 +111,9 @@ const getMatchPaths = pages => {
// More info in https://github.com/gatsbyjs/gatsby/issues/16097
// small speedup: don't bother traversing when no matchPaths found.
if (matchPathPages.length) {
const newMatches = []
pages.forEach((page, index) => {
const newMatches: IMatchPathEntry[] = []

pages.forEach((page: IGatsbyPage, index: number): void => {
const isInsideMatchPath = !!matchPathPages.find(
pageWithMatchPath =>
!page.matchPath && match(pageWithMatchPath.matchPath, page.path)
Expand Down Expand Up @@ -121,14 +152,17 @@ const getMatchPaths = pages => {
})
}

const createHash = (matchPaths, components) =>
const createHash = (
matchPaths: IGatsbyPageMatchPath[],
components: IGatsbyPageComponent[]
): string =>
crypto
.createHash(`md5`)
.update(JSON.stringify({ matchPaths, components }))
.digest(`hex`)

// Write out pages information.
const writeAll = async state => {
export const writeAll = async (state: IGatsbyState): Promise<boolean> => {
// console.log(`on requiresWriter progress`)
const { program } = state
const pages = [...state.pages.values()]
Expand Down Expand Up @@ -161,7 +195,7 @@ const preferDefault = m => m && m.default || m
\n\n`
syncRequires += `exports.components = {\n${components
.map(
c =>
(c: IGatsbyPageComponent): string =>
` "${
c.componentChunkName
}": ${hotMethod}(preferDefault(require("${joinPath(c.component)}")))`
Expand All @@ -174,7 +208,7 @@ const preferDefault = m => m && m.default || m
const preferDefault = m => m && m.default || m
\n`
asyncRequires += `exports.components = {\n${components
.map(c => {
.map((c: IGatsbyPageComponent): string => {
// we need a relative import path to keep contenthash the same if directory changes
const relativeComponentPath = path.relative(
path.join(program.directory, `.cache`),
Expand All @@ -188,7 +222,7 @@ const preferDefault = m => m && m.default || m
.join(`,\n`)}
}\n\n`

const writeAndMove = (file, data) => {
const writeAndMove = (file: string, data: string): Promise<void> => {
const destination = joinPath(program.directory, `.cache`, file)
const tmp = `${destination}.${Date.now()}`
return fs
Expand All @@ -206,7 +240,7 @@ const preferDefault = m => m && m.default || m
}

const debouncedWriteAll = _.debounce(
async () => {
async (): Promise<void> => {
const activity = reporter.activityTimer(`write out requires`, {
id: `requires-writer`,
})
Expand All @@ -229,31 +263,24 @@ const debouncedWriteAll = _.debounce(
* Start listening to CREATE/DELETE_PAGE events so we can rewrite
* files as required
*/
const startListener = () => {
emitter.on(`CREATE_PAGE`, () => {
export const startListener = (): void => {
emitter.on(`CREATE_PAGE`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})

emitter.on(`CREATE_PAGE_END`, () => {
emitter.on(`CREATE_PAGE_END`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})

emitter.on(`DELETE_PAGE`, () => {
emitter.on(`DELETE_PAGE`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})

emitter.on(`DELETE_PAGE_BY_PATH`, () => {
emitter.on(`DELETE_PAGE_BY_PATH`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})
}

module.exports = {
writeAll,
resetLastHash,
startListener,
getComponents,
}
2 changes: 1 addition & 1 deletion packages/gatsby/src/commands/develop-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { detectPortInUseAndPrompt } from "../utils/detect-port-in-use-and-prompt
import onExit from "signal-exit"
import queryUtil from "../query"
import queryWatcher from "../query/query-watcher"
import requiresWriter from "../bootstrap/requires-writer"
import * as requiresWriter from "../bootstrap/requires-writer"
import {
reportWebpackWarnings,
structureWebpackErrors,
Expand Down

0 comments on commit 08709a2

Please sign in to comment.