Heroku dev deps (#19431)
* fix: req.csrfToken doesn't always exist (e.g. 500 page) * feat: update dockerfile and add nextjs to build * fix: run linter * move @babel deps -> dev deps * move webpack looking things from deps -> dev deps * move pa11y-ci to optional dep * explicitly include optional deps for pa11y * allow heroku dev deps to be installed * fix: update postcss module * fix: update dockerfile build * tmp: disable renderReact * see if another deploy is slower/faster * move a few more packages to devDeps * upgrade to package-lock v2 * use dayjs instead of date-fns * move cross-env to devDeps * remove unused 'del' package * commit husky precommit hooks * add hrtime to clone-for-build.js * Revert "add hrtime to clone-for-build.js" This reverts commit 70ee647bacce833f4ed2f621f62c63c1d85e5413. * update babel/eslint * fix: remove unused plugin * try a .slugignore * fix: heroku-postbuild to use npm run build * fix: i cannot spell dereferenced * add .next/cache to heroku cacheDirectories * test cached build * remove aws-sdk, see what breaks * move jest-puppeteer to optional deps * fix: update browser-test.yml to use newer node version * move jimp to optional dependencies * move puppeteer to optional dependencies * fix: ci optional include * fix: bad copy pasta * remove previous react experiment * update tests/README.md with note about optional deps * bump node test version back to 14 * convert package-lock back to v1 * fix: use node 15.x to leverage npm optional deps * fix: optional dep install * test: see what happens with heroku/nodejs-typescript buildpack * back to heroku/nodejs buildpack * move jest to optional * revert jest move * remove .slugignore * cleanup dockerfile, move xlsx-population to optional, add comment about optional deps * Update Dockerfile Co-authored-by: James M. Greene <JamesMGreene@github.com> Co-authored-by: James M. Greene <JamesMGreene@github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ module.exports = {
|
||||
es2020: true,
|
||||
node: true
|
||||
},
|
||||
parser: 'babel-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'standard'
|
||||
|
||||
2
.github/workflows/browser-test.yml
vendored
2
.github/workflows/browser-test.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Run brower-test
|
||||
run: npm run browser-test
|
||||
|
||||
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
- name: Build the container
|
||||
run: docker build .
|
||||
run: docker build --target production .
|
||||
|
||||
2
.github/workflows/pa11y.yml
vendored
2
.github/workflows/pa11y.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Run build scripts
|
||||
run: npm run build
|
||||
|
||||
58
Dockerfile
58
Dockerfile
@@ -3,25 +3,62 @@
|
||||
# It uses two multi-stage builds: `install` and the main build to keep the image size down.
|
||||
|
||||
# --------------------------------------------------------------------------------
|
||||
# INSTALL IMAGE
|
||||
# A temporary image that installs production-only dependencies and builds the production-ready front-end bundles.
|
||||
# BASE IMAGE
|
||||
# --------------------------------------------------------------------------------
|
||||
FROM node:16.2.0-alpine as base
|
||||
|
||||
RUN apk add --no-cache make g++ git
|
||||
|
||||
FROM node:14-alpine as install
|
||||
RUN apk add --no-cache python make g++
|
||||
ENV NODE_ENV production
|
||||
WORKDIR /usr/src/docs
|
||||
|
||||
|
||||
# ---------------
|
||||
# ALL DEPS
|
||||
# ---------------
|
||||
FROM base as all_deps
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm ci
|
||||
|
||||
|
||||
# ---------------
|
||||
# PROD DEPS
|
||||
# ---------------
|
||||
FROM all_deps as prod_deps
|
||||
|
||||
RUN npm prune --production
|
||||
|
||||
|
||||
# ---------------
|
||||
# BUILDER
|
||||
# ---------------
|
||||
FROM all_deps as builder
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
COPY javascripts ./javascripts
|
||||
COPY stylesheets ./stylesheets
|
||||
COPY pages ./pages
|
||||
COPY components ./components
|
||||
COPY lib ./lib
|
||||
|
||||
# one part of the build relies on this content file to pull all-products
|
||||
COPY content/index.md ./content/index.md
|
||||
|
||||
COPY webpack.config.js ./webpack.config.js
|
||||
RUN npm ci --production
|
||||
COPY next.config.js ./next.config.js
|
||||
COPY tsconfig.json ./tsconfig.json
|
||||
|
||||
RUN npx tsc
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# --------------------------------------------------------------------------------
|
||||
# MAIN IMAGE
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
FROM node:14-alpine
|
||||
FROM node:16.2.0-alpine as production
|
||||
|
||||
# Let's make our home
|
||||
WORKDIR /usr/src/docs
|
||||
@@ -32,11 +69,12 @@ RUN chown node:node /usr/src/docs -R
|
||||
# This should be our normal running user
|
||||
USER node
|
||||
|
||||
# Copy our dependencies
|
||||
COPY --chown=node:node --from=install /usr/src/docs/node_modules /usr/src/docs/node_modules
|
||||
# Copy just our prod dependencies
|
||||
COPY --chown=node:node --from=prod_deps /usr/src/docs/node_modules /usr/src/docs/node_modules
|
||||
|
||||
# Copy our front-end code
|
||||
COPY --chown=node:node --from=install /usr/src/docs/dist /usr/src/docs/dist
|
||||
COPY --chown=node:node --from=builder /usr/src/docs/dist /usr/src/docs/dist
|
||||
COPY --chown=node:node --from=builder /usr/src/docs/.next /usr/src/docs/.next
|
||||
|
||||
# We should always be running in production mode
|
||||
ENV NODE_ENV production
|
||||
|
||||
1
app.json
1
app.json
@@ -2,7 +2,6 @@
|
||||
"name": "docs.github.com",
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"NPM_CONFIG_PRODUCTION": "true",
|
||||
"ENABLED_LANGUAGES": "en",
|
||||
"WEB_CONCURRENCY": "1"
|
||||
},
|
||||
|
||||
@@ -20,7 +20,11 @@ export const Contribution = () => {
|
||||
</a>
|
||||
<p className="color-text-secondary f6 mt-2">
|
||||
{t`or`}{' '}
|
||||
<a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener">
|
||||
<a
|
||||
href="https://github.com/github/docs/blob/main/CONTRIBUTING.md"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{t`to_guidelines`}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -50,7 +50,11 @@ export const Header = () => {
|
||||
<div className="width-full">
|
||||
<div className="d-inline-block width-full d-md-flex" style={{ zIndex: 1 }}>
|
||||
<div className="float-right d-md-none position-relative" style={{ zIndex: 3 }}>
|
||||
<ButtonOutline css onClick={() => setIsMenuOpen(!isMenuOpen)} aria-label="Navigation Menu">
|
||||
<ButtonOutline
|
||||
css
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
aria-label="Navigation Menu"
|
||||
>
|
||||
{isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />}
|
||||
</ButtonOutline>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import cx from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { format } from 'date-fns'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { ArrowRightIcon } from '@primer/octicons-react'
|
||||
import { FeaturedLink, useProductLandingContext } from 'components/context/ProductLandingContext'
|
||||
@@ -93,9 +93,9 @@ const ArticleList = ({ title, viewAllHref, articles }: ArticleListProps) => {
|
||||
{link.date && (
|
||||
<time
|
||||
className="tooltipped tooltipped-n color-text-tertiary text-mono mt-1"
|
||||
aria-label={format(new Date(link.date), 'PPP')}
|
||||
aria-label={dayjs(link.date).format('LLL')}
|
||||
>
|
||||
{format(new Date(link.date), 'MMMM dd')}
|
||||
{dayjs(link.date).format('MMMM DD')}
|
||||
</time>
|
||||
)}
|
||||
</a>
|
||||
|
||||
26
lib/page.js
26
lib/page.js
@@ -11,7 +11,6 @@ const Permalink = require('./permalink')
|
||||
const languages = require('./languages')
|
||||
const renderContent = require('./render-content')
|
||||
const processLearningTracks = require('./process-learning-tracks')
|
||||
const { renderReact } = require('./react/engine')
|
||||
const { productMap } = require('./all-products')
|
||||
const slash = require('slash')
|
||||
const statsd = require('./statsd')
|
||||
@@ -168,31 +167,8 @@ class Page {
|
||||
this.introLinks.overview = await renderContent(this.introLinks.rawOverview, context, { textOnly: true })
|
||||
}
|
||||
|
||||
let markdown = this.markdown
|
||||
|
||||
// If the article is interactive parse the React!
|
||||
if (this.interactive) {
|
||||
// Search for the react code comments to find the react components
|
||||
const reactComponents = markdown.match(/<!--react-->(.*?)<!--end-react-->/gs)
|
||||
|
||||
// Render each of the react components in the markdown
|
||||
await Promise.all(reactComponents.map(async (reactComponent) => {
|
||||
let componentStr = reactComponent
|
||||
|
||||
// Remove the React comment indicators
|
||||
componentStr = componentStr.replace('<!--react-->\n', '').replace('<!--react-->', '')
|
||||
componentStr = componentStr.replace('\n<!--end-react-->', '').replace('<!--end-react-->', '')
|
||||
|
||||
// Get the rendered component
|
||||
const renderedComponent = await renderReact(componentStr)
|
||||
|
||||
// Replace the react component with the rendered markdown
|
||||
markdown = markdown.replace(reactComponent, renderedComponent)
|
||||
}))
|
||||
}
|
||||
|
||||
context.relativePath = this.relativePath
|
||||
const html = await renderContent(markdown, context)
|
||||
const html = await renderContent(this.markdown, context)
|
||||
|
||||
// product frontmatter may contain liquid
|
||||
if (this.product) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
const babel = require('@babel/core')
|
||||
|
||||
const reactBabelOptions = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-transform-react-jsx',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/transform-runtime'
|
||||
]
|
||||
}
|
||||
|
||||
const transform = code =>
|
||||
babel.transform(code, reactBabelOptions).code
|
||||
|
||||
module.exports = {
|
||||
transform: transform,
|
||||
reactBabelOptions: reactBabelOptions
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
const { renderToString } = require('react-dom/server')
|
||||
const { transform } = require('./babel')
|
||||
const React = require('react')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dirTree = require('directory-tree')
|
||||
|
||||
// Name of directory for saving transformed components that should be gitignored
|
||||
const dist = 'dist'
|
||||
|
||||
// Build React components
|
||||
// This loops through the react components and transpiles them to /dist
|
||||
// so they can be used by Node.js when we do server side rendering
|
||||
const tree = dirTree('./react/')
|
||||
if (tree) {
|
||||
for (const index in tree.children) {
|
||||
const file = tree.children[index]
|
||||
if (file.type === 'file' && file.extension === '.js') {
|
||||
if (!fs.existsSync(path.join(dist, 'react'))) {
|
||||
fs.mkdirSync(path.join(dist, 'react'), { recursive: true })
|
||||
}
|
||||
const content = transform(fs.readFileSync(file.path, 'utf8'))
|
||||
fs.writeFileSync(path.join(dist, file.path), content)
|
||||
}
|
||||
}
|
||||
}
|
||||
// End Build React Components
|
||||
|
||||
// Register components
|
||||
const components = {
|
||||
// CodeBlock: require('../../dist/react/CodeBlock'),
|
||||
// CodeEditor: require('../../dist/react/CodeEditor')
|
||||
}
|
||||
|
||||
const renderReact = async componentStr => {
|
||||
// Get component name as string so we can use it in the class name
|
||||
// which will be needed later if we choose to do client side React hydration
|
||||
const componentName = componentStr.match(/<([a-zA-Z]+)\s/)[1]
|
||||
// Add the wrapper and class name so we can later use React hydration on the client
|
||||
// side
|
||||
const jsx = `<div className="react-component-${componentName}">\n${componentStr}\n</div>`
|
||||
|
||||
const component = transform(jsx)
|
||||
|
||||
// eslint-disable-next-line
|
||||
const getComponent = new Function(
|
||||
'React',
|
||||
...Object.keys(components),
|
||||
`${component.replace('React', 'return React')}`
|
||||
)
|
||||
|
||||
return renderToString(getComponent(React, ...Object.values(components)))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
renderReact
|
||||
}
|
||||
@@ -2,6 +2,11 @@ const { productIds } = require('./lib/all-products')
|
||||
const languages = require('./lib/languages')
|
||||
|
||||
module.exports = {
|
||||
// speed up production `next build` by ignoring typechecking during that step of build.
|
||||
// type-checking still occurs in the Dockerfile build
|
||||
typescript: {
|
||||
ignoreBuildErrors: true
|
||||
},
|
||||
i18n: {
|
||||
locales: Object.values(languages).map(({ code }) => code),
|
||||
defaultLocale: 'en'
|
||||
|
||||
13489
package-lock.json
generated
13489
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
71
package.json
71
package.json
@@ -8,19 +8,11 @@
|
||||
"url": "https://github.com/github/docs"
|
||||
},
|
||||
"license": "(MIT AND CC-BY-4.0)",
|
||||
"cacheDirectories": [
|
||||
"node_modules",
|
||||
".next/cache"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-syntax-class-properties": "^7.12.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.14.2",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.14.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.14.3",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@graphql-inspector/core": "^2.5.0",
|
||||
"@graphql-tools/load": "^6.2.8",
|
||||
"@primer/components": "^28.0.4",
|
||||
"@primer/css": "^16.2.0",
|
||||
"@primer/octicons": "^14.1.0",
|
||||
@@ -29,8 +21,6 @@
|
||||
"ajv-formats": "^2.1.0",
|
||||
"algoliasearch": "^4.9.1",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"browser-date-formatter": "^3.0.3",
|
||||
"change-case": "^4.1.2",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
@@ -39,13 +29,9 @@
|
||||
"connect-datadog": "0.0.9",
|
||||
"connect-slashes": "^1.4.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.4",
|
||||
"csurf": "^1.11.0",
|
||||
"date-fns": "^2.21.3",
|
||||
"directory-tree": "^2.2.9",
|
||||
"dayjs": "^1.10.4",
|
||||
"dotenv": "^9.0.2",
|
||||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^5.2.6",
|
||||
@@ -73,7 +59,6 @@
|
||||
"lunr": "^2.3.9",
|
||||
"lunr-languages": "^1.4.0",
|
||||
"mdast-util-from-markdown": "^0.8.4",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"morgan": "^1.10.0",
|
||||
"next": "^10.2.0",
|
||||
@@ -94,51 +79,54 @@
|
||||
"remark-gemoji-to-emoji": "^1.1.0",
|
||||
"remark-parse": "^7.0.2",
|
||||
"remark-rehype": "^5.0.0",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"revalidator": "^0.3.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rss-parser": "^3.12.0",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^11.1.1",
|
||||
"search-with-your-keyboard": "1.1.0",
|
||||
"semver": "^7.3.5",
|
||||
"slash": "^3.0.0",
|
||||
"strip-html-comments": "^1.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"throng": "^5.0.0",
|
||||
"unified": "^8.4.2",
|
||||
"unist-util-visit": "^2.0.3",
|
||||
"uuid": "^8.3.2",
|
||||
"walk-sync": "^2.2.0",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0"
|
||||
"walk-sync": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.2.7",
|
||||
"@actions/github": "^5.0.0",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/eslint-parser": "^7.14.3",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@graphql-inspector/core": "^2.5.0",
|
||||
"@graphql-tools/load": "^6.2.8",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/lodash": "^4.14.169",
|
||||
"@types/react": "^17.0.6",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
"async": "^3.2.0",
|
||||
"await-sleep": "0.0.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"chalk": "^4.1.1",
|
||||
"commander": "^7.2.0",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"count-array-values": "^1.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"csp-parse": "0.0.2",
|
||||
"css-loader": "^4.3.0",
|
||||
"csv-parse": "^4.15.4",
|
||||
"csv-parser": "^3.0.0",
|
||||
"dedent": "^0.7.0",
|
||||
"del": "^6.0.0",
|
||||
"domwaiter": "^1.3.0",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.23.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"glob": "^7.1.7",
|
||||
"graphql": "^15.5.0",
|
||||
@@ -151,31 +139,41 @@
|
||||
"jest": "^26.6.3",
|
||||
"jest-expect-message": "^1.0.2",
|
||||
"jest-github-actions-reporter": "^1.0.3",
|
||||
"jest-puppeteer": "^5.0.3",
|
||||
"jest-silent-reporter": "^0.5.0",
|
||||
"jest-slow-test-reporter": "^1.0.0",
|
||||
"jimp": "^0.16.1",
|
||||
"make-promises-safe": "^5.1.0",
|
||||
"mime": "^2.4.4",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"mock-express-response": "^0.3.0",
|
||||
"mockdate": "^3.0.5",
|
||||
"nock": "^13.0.11",
|
||||
"nodemon": "^2.0.7",
|
||||
"npm-merge-driver-install": "^2.0.1",
|
||||
"object-hash": "^2.1.1",
|
||||
"pa11y-ci": "^2.4.1",
|
||||
"postcss": "^8.2.15",
|
||||
"prettier": "^2.3.0",
|
||||
"process": "^0.11.10",
|
||||
"puppeteer": "^9.1.1",
|
||||
"replace": "^1.2.1",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"robots-parser": "^2.3.0",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^11.1.1",
|
||||
"start-server-and-test": "^1.12.2",
|
||||
"strip-ansi": "^7.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"supertest": "^6.1.3",
|
||||
"typescript": "^4.2.4",
|
||||
"url-template": "^2.0.8",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-middleware": "^4.2.0",
|
||||
"website-scraper": "^4.2.3",
|
||||
"website-scraper": "^4.2.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"jest-puppeteer": "^5.0.3",
|
||||
"jimp": "^0.16.1",
|
||||
"pa11y-ci": "^2.4.1",
|
||||
"puppeteer": "^9.1.1",
|
||||
"xlsx-populate": "^1.21.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -183,7 +181,8 @@
|
||||
"dev": "npm start",
|
||||
"debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --inspect server.js",
|
||||
"rest-dev": "script/rest/update-files.js && npm run dev",
|
||||
"build": "cross-env NODE_ENV=production npx webpack --mode production",
|
||||
"build": "npm run webpack-build && next build",
|
||||
"webpack-build": "cross-env NODE_ENV=production npx webpack --mode production",
|
||||
"start-all-languages": "cross-env NODE_ENV=development nodemon server.js",
|
||||
"lint": "eslint --fix . && prettier -w \"**/*.{yml,yaml}\" && npm run lint-tsc",
|
||||
"lint-translation": "TEST_TRANSLATION=true jest content/lint-files",
|
||||
|
||||
@@ -9,7 +9,7 @@ import '@primer/css/index.scss'
|
||||
|
||||
import { defaultThemeProps } from 'components/lib/getThemeProps'
|
||||
|
||||
type MyAppProps = AppProps & { csrfToken: string, themeProps: typeof defaultThemeProps }
|
||||
type MyAppProps = AppProps & { csrfToken: string; themeProps: typeof defaultThemeProps }
|
||||
const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => {
|
||||
return (
|
||||
<>
|
||||
@@ -43,9 +43,10 @@ const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => {
|
||||
MyApp.getInitialProps = async (appContext: AppContext) => {
|
||||
const { ctx } = appContext
|
||||
// calls page's `getInitialProps` and fills `appProps.pageProps`
|
||||
const appProps = await App.getInitialProps(appContext);
|
||||
const appProps = await App.getInitialProps(appContext)
|
||||
const req: any = ctx.req
|
||||
|
||||
return { ...appProps, themeProps: getThemeProps(ctx.req), csrfToken: (ctx.req as any).csrfToken() }
|
||||
return { ...appProps, themeProps: getThemeProps(req), csrfToken: req?.csrfToken?.() || '' }
|
||||
}
|
||||
|
||||
const SetTheme = ({ themeProps }: { themeProps: typeof defaultThemeProps }) => {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# React (Experimental)
|
||||
|
||||
The files in this directory are React components. We can use these components within Markdown files by adding `interactive: true` frontmatter to a Markdown file. Then, using the following syntax to embed the component:
|
||||
|
||||
```
|
||||
<!--react-->
|
||||
<ReactComponentHere some='prop' other={`prop`} />
|
||||
<!--end-react-->
|
||||
```
|
||||
|
||||
Theoretically React can be embedded anywhere on the site with a little bit of creativity but that is yet to be tested.
|
||||
|
||||
## Defining new components
|
||||
|
||||
Start by adding frontmatter `interactive:true` to any Markdown file that you want to support React. This prevents React render time from slowing down any other page and keeping the impact on performance very low.
|
||||
|
||||
1. Create the component in the `./react` directory
|
||||
2. Register the component for webpack in `./javascripts/index.js` so the component works with client side rendering
|
||||
3. Register the component in `./lib/react/engine.js` so the component works with server side rendering
|
||||
4. If the component needs to be evaluated client side, see [this example](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/react/CodeEditor.js) for how to ensure the client side component rendered by the server gets [Hydrated](https://reactjs.org/docs/react-dom.html#hydrate).
|
||||
|
||||
## A Complete Example
|
||||
|
||||
The following example demonstrates the addition of an interactive CodeEditor on one of our pages:
|
||||
|
||||
- [Defining the CodeEditor React Component](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/react/CodeEditor.js)
|
||||
- [Configuring the CodeEditor for client-side rendering](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/javascripts/index.js#L45)
|
||||
- [Configuring the CodeEditor for server-side rendering](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/lib/react/engine.js#L30)
|
||||
- CodeEditor added to a markdown file
|
||||
- [Enabling React components on a page](https://github.com/github/docs/blame/a48998c7890b71c8f58eda1fa31b50df348a0042/content/github/getting-started-with-github/access-permissions-on-github.md#L12)
|
||||
- [Adding the CodeEditor to a page](https://github.com/github/docs/blame/a48998c7890b71c8f58eda1fa31b50df348a0042/content/github/getting-started-with-github/access-permissions-on-github.md#L47)
|
||||
|
||||
## Gotchas
|
||||
|
||||
- When requiring React components from npm you will often need to explicitly call `require('component').default` to make sure you get the component
|
||||
- Some code examples you get from external React packages won't work out of the box, you often need to dig into the module and determine whether it's exporting to default or not which will in many cases cause you to need to `require('package').default`
|
||||
- `import` doesn't always work. Use `require` for a more consistent experience with React in this codebase.
|
||||
- If components require you to load CSS, you need to load that CSS in the `javascripts/index.js` file as shown [here](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/javascripts/index.js#L22)
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const XlsxPopulate = require('xlsx-populate')
|
||||
const XlsxPopulate = require('xlsx-populate') // this is an optional dependency, install with `npm i --include=optional`
|
||||
const readFrontmatter = require('../../lib/read-frontmatter')
|
||||
|
||||
const START_ROW = 2
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const walk = require('walk-sync')
|
||||
const jimp = require('jimp')
|
||||
const jimp = require('jimp') // this is an optional dependency, install with `npm i --include=optional`
|
||||
|
||||
// iterate through enterprise images from most recent to oldest
|
||||
// check if the image in the /assets/enterprise/... directory
|
||||
|
||||
@@ -10,6 +10,16 @@ by Facebook and used by many teams at GitHub. Jest is convenient in that it
|
||||
provides everything: a test runner, an assertion library, code coverage analysis,
|
||||
custom reporters for different types of test output, etc.
|
||||
|
||||
### Install optional dependencies
|
||||
|
||||
We typically rely on CI to run our tests, so we consider some large test-only
|
||||
dependencies **optional** (for example, puppeteer). In order to run the tests locally you'll
|
||||
need to make sure optional dependencies are installed by running:
|
||||
|
||||
```sh
|
||||
npm ci --include=optional
|
||||
```
|
||||
|
||||
### Running all the tests
|
||||
|
||||
Once you've followed the development instructions above, you can run the entire
|
||||
@@ -62,17 +72,18 @@ This test checks all internal links and image references in the English site. To
|
||||
```sh
|
||||
npx jest links-and-images
|
||||
```
|
||||
|
||||
It checks images, anchors, and links for every **version** of every **page**.
|
||||
|
||||
It reports five types of problems:
|
||||
|
||||
1. **Broken image references**
|
||||
* Example: `/assets/images/foo.png` where `foo.png` doesn't exist.
|
||||
- Example: `/assets/images/foo.png` where `foo.png` doesn't exist.
|
||||
2. **Broken same-page anchors**
|
||||
* Example: `#foo` where the page does not have a heading `Foo`.
|
||||
- Example: `#foo` where the page does not have a heading `Foo`.
|
||||
3. **Broken links due to page not found**
|
||||
* Example: `/github/using-git/foo` where there is no `foo.md` file at that path.
|
||||
- Example: `/github/using-git/foo` where there is no `foo.md` file at that path.
|
||||
4. **Broken links due to versioning**
|
||||
* Example: an unversioned link to a Dotcom-only article in a page that has Enterprise versions.
|
||||
- Example: an unversioned link to a Dotcom-only article in a page that has Enterprise versions.
|
||||
5. **Broken anchors on links**
|
||||
* Example: `/some/valid/link#bar` where the linked page can be found but it does not have a heading `Bar`.
|
||||
- Example: `/some/valid/link#bar` where the linked page can be found but it does not have a heading `Bar`.
|
||||
|
||||
@@ -2,7 +2,6 @@ const path = require('path')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const { EnvironmentPlugin, ProvidePlugin } = require('webpack')
|
||||
const { reactBabelOptions } = require('./lib/react/babel')
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
@@ -16,19 +15,9 @@ module.exports = {
|
||||
stats: 'errors-only',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
include: [
|
||||
path.resolve(__dirname, 'react')
|
||||
],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: reactBabelOptions
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
exclude: /(node_modules|bower_components|react)/,
|
||||
exclude: /(node_modules)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user