diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000000..2a372e586f19d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,25 @@ + +**/coverage/** +**/node_modules/** +bin/ +packages/*/dist/** +packages/*/lib/** +packages/*/scripts/** +**/dist/* +**/__testfixtures__/** +**/__tests__/fixtures/** +peril +docs +plop-templates +starters +www +benchmarks +e2e-tests +examples +integration-tests +**/*.d.ts + +packages/*/*.js +packages/gatsby-plugin-preload-fonts/prepare/*.js +packages/gatsby-image/withIEPolyfill/index.js +packages/gatsby/cache-dir/commonjs/**/* diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000000..5f114f29efc5a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,130 @@ +const TSEslint = require("@typescript-eslint/eslint-plugin") + +module.exports = { + parser: "babel-eslint", + extends: [ + "google", + "eslint:recommended", + "plugin:flowtype/recommended", + "plugin:react/recommended", + "prettier", + "prettier/flowtype", + "prettier/react", + ], + plugins: ["flowtype", "prettier", "react", "filenames"], + parserOptions: { + ecmaVersion: 2016, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + es6: true, + node: true, + jest: true, + }, + globals: { + before: true, + after: true, + spyOn: true, + __PATH_PREFIX__: true, + __BASE_PATH__: true, + __ASSET_PREFIX__: true, + }, + rules: { + "arrow-body-style": [ + "error", + "as-needed", + { requireReturnForObjectLiteral: true }, + ], + "no-unused-expressions": [ + "error", + { + allowTaggedTemplates: true, + }, + ], + "consistent-return": ["error"], + "filenames/match-regex": ["error", "^[a-z-\\d\\.]+$", true], + "no-console": "off", + "no-inner-declarations": "off", + "prettier/prettier": "error", + quotes: ["error", "backtick"], + "react/display-name": "off", + "react/jsx-key": "warn", + "react/no-unescaped-entities": "off", + "react/prop-types": "off", + "require-jsdoc": "off", + "valid-jsdoc": "off", + }, + overrides: [ + { + files: [ + "packages/**/gatsby-browser.js", + "packages/gatsby/cache-dir/**/*", + ], + env: { + browser: true, + }, + globals: { + ___loader: false, + ___emitter: false, + }, + }, + { + files: ["**/cypress/integration/**/*", "**/cypress/support/**/*"], + globals: { + cy: false, + Cypress: false, + }, + }, + { + files: ["*.ts", "*.tsx"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint/eslint-plugin"], + rules: { + ...TSEslint.configs.recommended.rules, + // This rule ensures that typescript types do not have semicolons + // at the end of their lines, since our prettier setup is to have no semicolons + // e.g., + // interface Foo { + // - baz: string; + // + baz: string + // } + "@typescript-eslint/member-delimiter-style": [ + "error", + { + multiline: { + delimiter: "none", + }, + }, + ], + // This ensures all interfaces are named with an I as a prefix + // e.g., + // interface IFoo {} + "@typescript-eslint/interface-name-prefix": [ + "error", + { prefixWithI: "always" }, + ], + "@typescript-eslint/no-empty-function": "off", + // This ensures that we always type the return type of functions + // a high level focus of our TS setup is typing fn inputs and outputs. + "@typescript-eslint/explicit-function-return-type": "error", + // This forces us to use interfaces over types aliases for object defintions. + // Type is still useful for opaque types + // e.g., + // type UUID = string + "@typescript-eslint/consistent-type-definitions": [ + "error", + "interface", + ], + }, + }, + ], + settings: { + react: { + version: "16.4.2", + }, + }, +} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index c953f88345f75..0000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "parser": "babel-eslint", - "extends": [ - "google", - "eslint:recommended", - "plugin:flowtype/recommended", - "plugin:react/recommended", - "prettier", - "prettier/flowtype", - "prettier/react" - ], - "plugins": ["flowtype", "prettier", "react", "filenames"], - "parserOptions": { - "ecmaVersion": 2016, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "env": { - "browser": true, - "es6": true, - "node": true, - "jest": true - }, - "globals": { - "before": true, - "after": true, - "spyOn": true, - "__PATH_PREFIX__": true, - "__BASE_PATH__": true, - "__ASSET_PREFIX__": true - }, - "rules": { - "arrow-body-style": [ - "error", - "as-needed", - { "requireReturnForObjectLiteral": true } - ], - "no-unused-expressions": [ - "error", - { - "allowTaggedTemplates": true - } - ], - "consistent-return": ["error"], - "filenames/match-regex": ["error", "^[a-z-\\d\\.]+$", true], - "no-console": "off", - "no-inner-declarations": "off", - "prettier/prettier": "error", - "quotes": ["error", "backtick"], - "react/display-name": "off", - "react/jsx-key": "warn", - "react/no-unescaped-entities": "off", - "react/prop-types": "off", - "require-jsdoc": "off", - "valid-jsdoc": "off" - }, - "overrides": [ - { - "files": [ - "packages/**/gatsby-browser.js", - "packages/gatsby/cache-dir/**/*" - ], - "env": { - "browser": true - }, - "globals": { - "___loader": false, - "___emitter": false - } - }, - { - "files": ["**/cypress/integration/**/*", "**/cypress/support/**/*"], - "globals": { - "cy": false, - "Cypress": false - } - } - ], - "settings": { - "react": { - "version": "16.4.2" - } - } -} diff --git a/.gitignore b/.gitignore index 6811179d0d151..1336d6f4525fb 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ node_modules/ # misc .serverless/ +.eslintcache # lock files yarn.lock diff --git a/package.json b/package.json index 9ec23879ba7b5..257e58bfc7120 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "@types/jest": "^24.0.23", "@types/node": "^12.12.11", "@types/webpack": "^4.41.0", - "@typescript-eslint/eslint-plugin": "^2.11.0", - "@typescript-eslint/parser": "^2.11.0", + "@typescript-eslint/eslint-plugin": "^2.12.0", + "@typescript-eslint/parser": "^2.12.0", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "chalk": "^2.4.2", @@ -62,7 +62,7 @@ "private": true, "lint-staged": { "*.{js,jsx}": [ - "eslint --ignore-path .gitignore --ignore-path .prettierignore --fix", + "eslint --cache --ext .js,.jsx,.ts,.tsx --fix", "git add" ], "*.{md,css,scss,yaml,yml}": [ @@ -94,7 +94,7 @@ "lerna": "lerna", "lerna-prepare": "lerna run prepare", "lint": "npm-run-all --continue-on-error -p lint:code lint:other", - "lint:code": "eslint --ignore-path .gitignore --ignore-path .prettierignore --ext .js,.jsx .", + "lint:code": "eslint --cache --ext .js,.jsx,.ts,.tsx .", "lint:scripts": "sh scripts/lint-shell-scripts.sh", "lint:other": "npm run prettier -- --check", "markdown": "md-magic --path \"starters/**/*.md\"", diff --git a/packages/babel-preset-gatsby-package/__tests__/index.js b/packages/babel-preset-gatsby-package/__tests__/index.js index 256fab822db0f..e6e692b62cf17 100644 --- a/packages/babel-preset-gatsby-package/__tests__/index.js +++ b/packages/babel-preset-gatsby-package/__tests__/index.js @@ -25,14 +25,16 @@ describe(`babel-preset-gatsby-package`, () => { it(`can pass custom nodeVersion target`, () => { process.env.BABEL_ENV = `production` - + const nodeVersion = `6.0` const { presets } = preset(null, { - nodeVersion + nodeVersion, }) - const [_, opts] = presets.find(preset => [].concat(preset).includes('@babel/preset-env')) - + const [, opts] = presets.find(preset => + [].concat(preset).includes(`@babel/preset-env`) + ) + expect(opts.targets.node).toBe(nodeVersion) }) }) diff --git a/packages/gatsby/cache-dir/__tests__/static-entry.js b/packages/gatsby/cache-dir/__tests__/static-entry.js index 9149015bf2e7c..3b88e4d760e19 100644 --- a/packages/gatsby/cache-dir/__tests__/static-entry.js +++ b/packages/gatsby/cache-dir/__tests__/static-entry.js @@ -321,6 +321,7 @@ describe(`sanitizeComponents`, () => { const sanitizedComponents = sanitizeComponents([ , diff --git a/packages/gatsby/src/bootstrap/page-hot-reloader.js b/packages/gatsby/src/bootstrap/page-hot-reloader.js index b3ac0d004dfd4..3e8ae7b980695 100644 --- a/packages/gatsby/src/bootstrap/page-hot-reloader.js +++ b/packages/gatsby/src/bootstrap/page-hot-reloader.js @@ -7,28 +7,6 @@ const report = require(`gatsby-cli/lib/reporter`) let pagesDirty = false let graphql -emitter.on(`CREATE_NODE`, action => { - if (action.payload.internal.type !== `SitePage`) { - pagesDirty = true - } -}) -emitter.on(`DELETE_NODE`, action => { - if (action.payload.internal.type !== `SitePage`) { - pagesDirty = true - // Make a fake API call to trigger `API_RUNNING_QUEUE_EMPTY` being called. - // We don't want to call runCreatePages here as there might be work in - // progress. So this is a safe way to make sure runCreatePages gets called - // at a safe time. - apiRunnerNode(`FAKE_API_CALL`) - } -}) - -emitter.on(`API_RUNNING_QUEUE_EMPTY`, () => { - if (pagesDirty) { - runCreatePages() - } -}) - const runCreatePages = async () => { pagesDirty = false @@ -65,4 +43,25 @@ const runCreatePages = async () => { module.exports = graphqlRunner => { graphql = graphqlRunner + emitter.on(`CREATE_NODE`, action => { + if (action.payload.internal.type !== `SitePage`) { + pagesDirty = true + } + }) + emitter.on(`DELETE_NODE`, action => { + if (action.payload.internal.type !== `SitePage`) { + pagesDirty = true + // Make a fake API call to trigger `API_RUNNING_QUEUE_EMPTY` being called. + // We don't want to call runCreatePages here as there might be work in + // progress. So this is a safe way to make sure runCreatePages gets called + // at a safe time. + apiRunnerNode(`FAKE_API_CALL`) + } + }) + + emitter.on(`API_RUNNING_QUEUE_EMPTY`, () => { + if (pagesDirty) { + runCreatePages() + } + }) } diff --git a/packages/gatsby/src/commands/develop.ts b/packages/gatsby/src/commands/develop.ts index 514a7694e4599..9b0aba1d9aac3 100644 --- a/packages/gatsby/src/commands/develop.ts +++ b/packages/gatsby/src/commands/develop.ts @@ -3,6 +3,8 @@ import fs from "fs" import openurl from "better-opn" import chokidar from "chokidar" +import webpackHotMiddleware from "webpack-hot-middleware" +import webpackDevMiddleware from "webpack-dev-middleware" import { PackageJson } from "gatsby" import glob from "glob" import express from "express" @@ -30,6 +32,9 @@ import WorkerPool from "../utils/worker/pool" import http from "http" import https from "https" +import bootstrapSchemaHotReloader from "../bootstrap/schema-hot-reloader" +import bootstrapPageHotReloader from "../bootstrap/page-hot-reloader" +import developStatic from "./develop-static" import withResolverContext from "../schema/context" import sourceNodes from "../utils/source-nodes" import createSchemaCustomization from "../utils/create-schema-customization" @@ -83,9 +88,9 @@ onExit(() => { telemetry.trackCli(`DEVELOP_STOP`) }) -const waitJobsFinished = () => +const waitJobsFinished = (): Promise => new Promise(resolve => { - const onEndJob = () => { + const onEndJob = (): void => { if (store.getState().jobs.active.length === 0) { resolve() emitter.off(`END_JOB`, onEndJob) @@ -97,13 +102,19 @@ const waitJobsFinished = () => type ActivityTracker = any // TODO: Replace this with proper type once reporter is typed -async function startServer(program: IProgram) { +interface IServer { + compiler: webpack.Compiler + listener: http.Server | https.Server + webpackActivity: ActivityTracker +} + +async function startServer(program: IProgram): Promise { const indexHTMLActivity = report.phantomActivity(`building index.html`, {}) indexHTMLActivity.start() const directory = program.directory const directoryPath = withBasePath(directory) const workerPool = WorkerPool.create() - const createIndexHtml = async (activity: ActivityTracker) => { + const createIndexHtml = async (activity: ActivityTracker): Promise => { try { await buildHTML.buildPages({ program, @@ -155,7 +166,7 @@ async function startServer(program: IProgram) { const app = express() app.use(telemetry.expressMiddleware(`DEVELOP`)) app.use( - require(`webpack-hot-middleware`)(compiler, { + webpackHotMiddleware(compiler, { log: false, path: `/__webpack_hmr`, heartbeat: 10 * 1000, @@ -185,26 +196,28 @@ async function startServer(program: IProgram) { app.use( graphqlEndpoint, - graphqlHTTP(() => { - const { schema, schemaCustomization } = store.getState() + graphqlHTTP( + (): graphqlHTTP.OptionsData => { + const { schema, schemaCustomization } = store.getState() - return { - schema, - graphiql: false, - context: withResolverContext({ + return { schema, - schemaComposer: schemaCustomization.composer, - context: {}, - customContext: schemaCustomization.context, - }), - customFormatErrorFn(err) { - return { - ...formatError(err), - stack: err.stack ? err.stack.split(`\n`) : [], - } - }, + graphiql: false, + context: withResolverContext({ + schema, + schemaComposer: schemaCustomization.composer, + context: {}, + customContext: schemaCustomization.context, + }), + customFormatErrorFn(err): unknown { + return { + ...formatError(err), + stack: err.stack ? err.stack.split(`\n`) : [], + } + }, + } } - }) + ) ) /** @@ -213,7 +226,7 @@ async function startServer(program: IProgram) { * If no GATSBY_REFRESH_TOKEN env var is available, then no Authorization header is required **/ const REFRESH_ENDPOINT = `/__refresh` - const refresh = async (req: express.Request) => { + const refresh = async (req: express.Request): Promise => { let activity = report.activityTimer(`createSchemaCustomization`, {}) activity.start() await createSchemaCustomization({ @@ -249,10 +262,10 @@ async function startServer(program: IProgram) { // This can lead to serving stale html files during development. // // We serve by default an empty index.html that sets up the dev environment. - app.use(require(`./develop-static`)(`public`, { index: false })) + app.use(developStatic(`public`, { index: false })) app.use( - require(`webpack-dev-middleware`)(compiler, { + webpackDevMiddleware(compiler, { logLevel: `silent`, publicPath: devConfig.output.publicPath, watchOptions: devConfig.devServer @@ -277,7 +290,7 @@ async function startServer(program: IProgram) { const proxiedUrl = url + req.originalUrl const { // remove `host` from copied headers - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars headers: { host, ...headers }, method, } = req @@ -286,11 +299,11 @@ async function startServer(program: IProgram) { got .stream(proxiedUrl, { headers, method, decompress: false }) .on(`response`, response => - res.writeHead(response.statusCode!, response.headers) + res.writeHead(response.statusCode || 200, response.headers) ) .on(`error`, (err, _, response) => { if (response) { - res.writeHead(response.statusCode!, response.headers) + res.writeHead(response.statusCode || 400, response.headers) } else { const message = `Error when trying to proxy request "${req.originalUrl}" to "${proxiedUrl}"` @@ -326,7 +339,7 @@ async function startServer(program: IProgram) { * Set up the HTTP server and socket.io. * If a SSL cert exists in program, use it with `createServer`. **/ - let server = program.ssl + const server = program.ssl ? https.createServer(program.ssl, app) : new http.Server(app) @@ -348,7 +361,7 @@ async function startServer(program: IProgram) { return { compiler, listener, webpackActivity } } -module.exports = async (program: IProgram) => { +module.exports = async (program: IProgram): Promise => { initTracer(program.openTracingConfigFile) report.pendingActivity({ id: `webpack-develop` }) telemetry.trackCli(`DEVELOP_START`) @@ -396,10 +409,10 @@ module.exports = async (program: IProgram) => { const { graphqlRunner } = await bootstrap(program) // Start the createPages hot reloader. - require(`../bootstrap/page-hot-reloader`)(graphqlRunner) + bootstrapPageHotReloader(graphqlRunner) // Start the schema hot reloader. - require(`../bootstrap/schema-hot-reloader`)() + bootstrapSchemaHotReloader() await queryUtil.initialProcessQueries() @@ -415,7 +428,7 @@ module.exports = async (program: IProgram) => { let { compiler, webpackActivity } = await startServer(program) - type PreparedUrls = { + interface IPreparedUrls { lanUrlForConfig: string lanUrlForTerminal: string localUrlForTerminal: string @@ -423,18 +436,18 @@ module.exports = async (program: IProgram) => { } function prepareUrls( - protocol: "http" | "https", + protocol: `http` | `https`, host: string, port: number - ): PreparedUrls { - const formatUrl = (hostname: string) => + ): IPreparedUrls { + const formatUrl = (hostname: string): string => url.format({ protocol, hostname, port, pathname: `/`, }) - const prettyPrintUrl = (hostname: string) => + const prettyPrintUrl = (hostname: string): string => url.format({ protocol, hostname, @@ -484,7 +497,7 @@ module.exports = async (program: IProgram) => { } } - function printInstructions(appName: string, urls: PreparedUrls) { + function printInstructions(appName: string, urls: IPreparedUrls): void { console.log() console.log(`You can now view ${chalk.bold(appName)} in the browser.`) console.log() @@ -533,8 +546,9 @@ module.exports = async (program: IProgram) => { console.log() } - function printDeprecationWarnings() { - const deprecatedApis: ["boundActionCreators", "pathContext"] = [ + function printDeprecationWarnings(): void { + type DeprecatedAPIList = ["boundActionCreators", "pathContext"] // eslint-disable-line + const deprecatedApis: DeprecatedAPIList = [ `boundActionCreators`, `pathContext`, ] @@ -617,7 +631,7 @@ module.exports = async (program: IProgram) => { if (isSuccessful && isFirstCompile) { printInstructions( - program.sitePackageJson.name || "(Unnamed package)", + program.sitePackageJson.name || `(Unnamed package)`, urls ) printDeprecationWarnings() diff --git a/yarn.lock b/yarn.lock index cb70aded87375..3fc6ca013a9ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4121,6 +4121,17 @@ regexpp "^3.0.0" tsutils "^3.17.1" +"@typescript-eslint/eslint-plugin@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.12.0.tgz#0da7cbca7b24f4c6919e9eb31c704bfb126f90ad" + integrity sha512-1t4r9rpLuEwl3hgt90jY18wJHSyb0E3orVL3DaqwmpiSDHmHiSspVsvsFF78BJ/3NNG3qmeso836jpuBWYziAA== + dependencies: + "@typescript-eslint/experimental-utils" "2.12.0" + eslint-utils "^1.4.3" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + "@typescript-eslint/experimental-utils@2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.11.0.tgz#cef18e6b122706c65248a5d8984a9779ed1e52ac" @@ -4130,6 +4141,15 @@ "@typescript-eslint/typescript-estree" "2.11.0" eslint-scope "^5.0.0" +"@typescript-eslint/experimental-utils@2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.12.0.tgz#e0a76ffb6293e058748408a191921e453c31d40d" + integrity sha512-jv4gYpw5N5BrWF3ntROvCuLe1IjRenLy5+U57J24NbPGwZFAjhnM45qpq0nDH1y/AZMb3Br25YiNVwyPbz6RkA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.12.0" + eslint-scope "^5.0.0" + "@typescript-eslint/parser@^2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.11.0.tgz#cdcc3be73ee31cbef089af5ff97ccaa380ef6e8b" @@ -4140,6 +4160,16 @@ "@typescript-eslint/typescript-estree" "2.11.0" eslint-visitor-keys "^1.1.0" +"@typescript-eslint/parser@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.12.0.tgz#393f1604943a4ca570bb1a45bc8834e9b9158884" + integrity sha512-lPdkwpdzxEfjI8TyTzZqPatkrswLSVu4bqUgnB03fHSOwpC7KSerPgJRgIAf11UGNf7HKjJV6oaPZI4AghLU6g== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.12.0" + "@typescript-eslint/typescript-estree" "2.12.0" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/typescript-estree@2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.11.0.tgz#21ada6504274cd1644855926312c798fc697e9fb" @@ -4153,6 +4183,19 @@ semver "^6.3.0" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.12.0.tgz#bd9e547ccffd17dfab0c3ab0947c80c8e2eb914c" + integrity sha512-rGehVfjHEn8Frh9UW02ZZIfJs6SIIxIu/K1bbci8rFfDE/1lQ8krIJy5OXOV3DVnNdDPtoiPOdEANkLMrwXbiQ== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash.unescape "4.0.1" + semver "^6.3.0" + tsutils "^3.17.1" + "@verdaccio/commons-api@8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@verdaccio/commons-api/-/commons-api-8.3.0.tgz#3a4b07b63561b21bfc8242330da528e12788635e"