Repository: patrickleet/streaming-ssr-react-styled-components
Branch: master
Commit: dc497fc7c466
Files: 46
Total size: 29.3 KB
Directory structure:
gitextract_yyu56on6/
├── .babelrc
├── .dockerignore
├── .eslintignore
├── .gitignore
├── .prettierignore
├── .travis.yml
├── Dockerfile
├── Makefile
├── README.md
├── __tests__/
│ ├── setup.js
│ └── unit/
│ ├── app/
│ │ ├── App.jsx
│ │ ├── client.js
│ │ ├── components/
│ │ │ ├── Header.jsx
│ │ │ └── Page.jsx
│ │ ├── pages/
│ │ │ ├── About.jsx
│ │ │ ├── Error.jsx
│ │ │ ├── Home.jsx
│ │ │ └── Loading.jsx
│ │ └── styles.js
│ └── server/
│ ├── index.js
│ └── lib/
│ ├── client.js
│ ├── server.js
│ └── ssr.js
├── app/
│ ├── App.jsx
│ ├── client.js
│ ├── components/
│ │ ├── Header.jsx
│ │ └── Page.jsx
│ ├── imported.js
│ ├── index.html
│ ├── pages/
│ │ ├── About.jsx
│ │ ├── Error.jsx
│ │ ├── Home.jsx
│ │ └── Loading.jsx
│ └── styles.js
├── docker-compose.builder.yml
├── docker-compose.yml
├── jest.json
├── nginx/
│ ├── Dockerfile
│ ├── entrypoint.sh
│ └── nginx.conf.template
├── package.json
├── renovate.json
└── server/
├── index.js
└── lib/
├── client.js
├── server.js
└── ssr.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"env": {
"test": {
"presets":[
["@babel/preset-env"],
["@babel/preset-react"],
],
"plugins": [
["@babel/plugin-syntax-dynamic-import"]
]
},
"server": {
"plugins": ["react-imported-component/babel", "@babel/plugin-syntax-dynamic-import"]
},
"client": {
"plugins": [
["react-imported-component/babel"]
]
}
}
}
================================================
FILE: .dockerignore
================================================
.cache
coverage
dist
node_modules
================================================
FILE: .eslintignore
================================================
app/imported.js
dist
coverage
node_modules
================================================
FILE: .gitignore
================================================
node_modules
*.log
.cache
dist
coverage
================================================
FILE: .prettierignore
================================================
app/imported.js
dist
coverage
node_modules
================================================
FILE: .travis.yml
================================================
jobs:
include:
- language: node_js
node_js:
- 12
before_script:
- npm run build
after_success:
- npm i -g codecov
- codecov
- services:
- docker
script:
- docker build . -t ssr
- services:
- docker
script:
- docker build . -f nginx/Dockerfile -t nginx
================================================
FILE: Dockerfile
================================================
FROM node:12.22.1-alpine AS build
RUN apk add --update --no-cache \
python \
make \
g++ \
git
WORKDIR /src
COPY ./package* ./
RUN npm ci
COPY . .
RUN npm run lint
RUN npm run build
RUN npm run test
RUN npm prune --production
FROM node:12.22.1-alpine
RUN apk add --update --no-cache curl
EXPOSE 1234
WORKDIR /usr/src/service
COPY --from=build /src/node_modules node_modules
COPY --from=build /src/dist dist
HEALTHCHECK --interval=5s \
--timeout=5s \
--retries=6 \
CMD curl -fs http://localhost:1234/ || exit 1
USER node
CMD ["node", "./dist/server/index.js"]
================================================
FILE: Makefile
================================================
setup:
docker volume create nodemodules
install:
docker-compose -f docker-compose.builder.yml run --rm install
dev:
docker-compose up
down:
docker-compose down
================================================
FILE: README.md
================================================
# streaming-ssr-react-styled-components
Learn how this boilerplate was created with my tutorials on HackerNoon!
* [Part 1: Move over Next.js and Webpack!!](https://hackernoon.com/move-over-next-js-and-webpack-ba367f07545)
* [Part 2: A Better Way to Develop Node.js with Docker And Keep Your Hot Code Reloading](https://hackernoon.com/a-better-way-to-develop-node-js-with-docker-cd29d3a0093)
* [Part 3: Enforcing Code Quality for Node.js - Using Linting, Formatting, and Unit Testing with Code Coverage to Enforce Quality Standards](https://hackernoon.com/enforcing-code-quality-for-node-js-c3b837d7ae17)
* [Part 4: The 100% Code Coverage Myth](https://hackernoon.com/the-100-code-coverage-myth-900b83d20d3d)
* [Part 5: A Tale of Two (Docker Multi-Stage Build) Layers - Production Ready Dockerfiles for Node.js using SSR or Nginx](https://hackernoon.com/a-tale-of-two-docker-multi-stage-build-layers-85348a409c84)
* [Part 6: Bring In The Bots, And Let Them Maintain Our Code!](https://hackernoon.com/bring-in-the-bots-and-let-them-maintain-our-code-gh3s33n9)
UPDATE: Sorry everyone, the Hackernoon formatting got messed up in the import, and I THINK they are all fixed now - can you please point out to me if something is not?
Just in case...
You can find the original versions of the articles on Medium with the original formatting at the following links:
* https://medium.com/hackernoon/move-over-next-js-and-webpack-ba367f07545
* https://medium.com/hackernoon/a-better-way-to-develop-node-js-with-docker-cd29d3a0093
* https://medium.com/hackernoon/enforcing-code-quality-for-node-js-c3b837d7ae17
* https://medium.com/hackernoon/the-100-code-coverage-myth-900b83d20d3d
* https://medium.com/hackernoon/a-tale-of-two-docker-multi-stage-build-layers-85348a409c84
Sorry for the inconvenience in tracking these down!
## tl;dr;
### Developing with Docker
```
# setup only needs to be run once
make setup
make install
make dev
```
### Developing Locally
```
npm i
npm run dev
```
================================================
FILE: __tests__/setup.js
================================================
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
================================================
FILE: __tests__/unit/app/App.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import App, { renderAboutPage } from 'app/App.jsx'
describe('app/App.jsx', () => {
it('renders App', () => {
expect(App).toBeDefined()
const tree = shallow(<App />)
expect(tree).not.toBeNull()
})
it('#renderAboutPage', () => {
const tree = shallow(renderAboutPage())
expect(tree).not.toBeNull()
})
})
================================================
FILE: __tests__/unit/app/client.js
================================================
import React from 'react'
import fs from 'fs'
import path from 'path'
import { start, hydrate } from 'app/client'
import { JSDOM } from 'jsdom'
jest.mock('react-dom')
jest.mock('react-imported-component')
jest.mock('app/imported.js')
// mock DOM with actual index.html contents
const pathToIndex = path.join(process.cwd(), 'app', 'index.html')
const indexHTML = fs.readFileSync(pathToIndex).toString()
const DOM = new JSDOM(indexHTML)
const document = DOM.window.document
// this doesn't contribute to coverage, but we
// should know if it changes as it would
// cause our app to break
describe('app/index.html', () => {
it('has element with id "app"', () => {
const element = document.getElementById('app')
expect(element.id).toBe('app')
})
})
describe('app/client.js', () => {
// Reset counts of mock calls after each test
afterEach(() => {
jest.clearAllMocks()
})
describe('#start', () => {
it('renders when in development and accepts hot module reloads', () => {
// this is mocked above, so require gets the mock version
// so we can see if its functions are called
const ReactDOM = require('react-dom')
// mock module.hot
const module = {
hot: {
accept: jest.fn()
}
}
// mock options
const options = {
isProduction: false,
module,
document
}
start(options)
expect(ReactDOM.render).toBeCalled()
expect(module.hot.accept).toBeCalled()
})
it('hydrates when in production does not accept hot module reloads', () => {
const ReactDOM = require('react-dom')
const importedComponent = require('react-imported-component')
importedComponent.rehydrateMarks.mockImplementation(() =>
Promise.resolve()
)
// mock module.hot
const module = {}
// mock rehydrate function
const hydrate = jest.fn()
// mock options
const options = {
isProduction: true,
module,
document,
hydrate
}
start(options)
expect(ReactDOM.render).not.toBeCalled()
expect(hydrate).toBeCalled()
})
})
describe('#hydrate', () => {
it('uses ReactDOM to hydrate given element with an app', () => {
const ReactDOM = require('react-dom')
const element = document.getElementById('app')
const app = <div />
const doHydrate = hydrate(app, element)
expect(typeof doHydrate).toBe('function')
doHydrate()
expect(ReactDOM.hydrate).toBeCalledWith(app, element)
})
})
})
================================================
FILE: __tests__/unit/app/components/Header.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import Header from 'app/components/Header.jsx'
describe('app/components/Header.jsx', () => {
it('renders Header component', () => {
expect(Header).toBeDefined()
const tree = shallow(<Header />)
expect(tree.find('Page')).toBeDefined()
expect(tree.text()).toContain('Stream things!')
})
})
================================================
FILE: __tests__/unit/app/components/Page.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import Page from 'app/components/Page.jsx'
describe('app/components/Page.jsx', () => {
it('renders Page component', () => {
expect(Page).toBeDefined()
const tree = shallow(<Page />)
expect(tree).toBeDefined()
})
})
================================================
FILE: __tests__/unit/app/pages/About.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import About from 'app/pages/About.jsx'
describe('app/pages/About.jsx', () => {
it('renders About page', () => {
expect(About).toBeDefined()
const tree = shallow(<About />)
expect(tree.find('Page')).toBeDefined()
expect(
tree
.find('Helmet')
.find('title')
.text()
).toEqual('About Page')
expect(tree.find('div').text()).toEqual('This is the about page')
})
})
================================================
FILE: __tests__/unit/app/pages/Error.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import Error from 'app/pages/Error.jsx'
describe('app/pages/Error.jsx', () => {
it('renders Error page', () => {
expect(Error).toBeDefined()
const tree = shallow(<Error />)
expect(tree.find('Page')).toBeDefined()
expect(tree.text()).toEqual('Error!')
})
})
================================================
FILE: __tests__/unit/app/pages/Home.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import Home from 'app/pages/Home.jsx'
describe('app/pages/Home.jsx', () => {
it('renders Home page', () => {
expect(Home).toBeDefined()
const tree = shallow(<Home />)
expect(tree.find('Page')).toBeDefined()
expect(
tree
.find('Helmet')
.find('title')
.text()
).toEqual('Home Page')
expect(tree.find('div').text()).toEqual('Follow me at @patrickleet')
expect(
tree
.find('div')
.find('a')
.text()
).toEqual('@patrickleet')
})
})
================================================
FILE: __tests__/unit/app/pages/Loading.jsx
================================================
import React from 'react'
import { shallow } from 'enzyme'
import Loading from 'app/pages/Loading.jsx'
describe('app/pages/Loading.jsx', () => {
it('renders Loading page', () => {
expect(Loading).toBeDefined()
const tree = shallow(<Loading />)
expect(tree.find('Page')).toBeDefined()
expect(tree.text()).toEqual('Loading...')
})
})
================================================
FILE: __tests__/unit/app/styles.js
================================================
import React from 'react'
import { shallow } from 'enzyme'
import { GlobalStyles } from 'app/styles.js'
describe('app/styles.js', () => {
it('renders styles page', () => {
expect(GlobalStyles).toBeDefined()
const tree = shallow(<GlobalStyles />)
expect(tree).not.toBeNull()
})
})
================================================
FILE: __tests__/unit/server/index.js
================================================
import { onListen } from 'server/index'
jest.mock('llog')
jest.mock('server/lib/server', () => ({
server: {
use: jest.fn(),
get: jest.fn(),
listen: jest.fn()
},
serveStatic: jest.fn(() => "static/path")
}))
jest.mock('server/lib/ssr')
describe('server/index.js', () => {
it('main', () => {
const { server, serveStatic } = require('server/lib/server')
expect(server.use).toBeCalledWith('/dist/client', "static/path")
expect(serveStatic).toBeCalledWith(`${process.cwd()}/dist/client`)
expect(server.get).toBeCalledWith('/*', expect.any(Function))
expect(server.listen).toBeCalledWith(1234, expect.any(Function))
})
it('onListen', () => {
const log = require('llog')
onListen(4000)()
expect(log.info).toBeCalledWith('Listening on port 4000...')
})
})
================================================
FILE: __tests__/unit/server/lib/client.js
================================================
import { getHTMLFragments } from 'server/lib/client.js'
describe('client', () => {
it('exists', () => {
const drainHydrateMarks = '<!-- mock hydrate marks -->'
const [start, end] = getHTMLFragments({ drainHydrateMarks })
expect(start).toContain('<head>')
expect(start).toContain(drainHydrateMarks)
expect(end).toContain('script id="js-entrypoint"')
})
})
================================================
FILE: __tests__/unit/server/lib/server.js
================================================
import express from 'express'
import { server, serveStatic } from 'server/lib/server.js'
describe('server/lib/server', () => {
it('should provide server APIs to use', () => {
expect(server).toBeDefined()
expect(server.use).toBeDefined()
expect(server.get).toBeDefined()
expect(server.listen).toBeDefined()
expect(serveStatic).toEqual(express.static)
})
})
================================================
FILE: __tests__/unit/server/lib/ssr.js
================================================
/**
* @jest-environment node
*/
import defaultSSR, { ssr, write, end } from 'server/lib/ssr.js'
jest.mock('llog')
const mockReq = {
originalUrl: '/'
}
const mockRes = {
redirect: jest.fn(),
status: jest.fn(),
end: jest.fn(),
write: jest.fn(),
on: jest.fn(),
removeListener: jest.fn(),
emit: jest.fn()
}
describe('server/lib/ssr.js', () => {
describe('ssr', () => {
it('redirects when context.url is set', () => {
const req = Object.assign({}, mockReq)
const res = Object.assign({}, mockRes)
const getApplicationStream = jest.fn((originalUrl, context) => {
context.url = '/redirect'
})
const doSSR = ssr(getApplicationStream)
expect(typeof doSSR).toBe('function')
doSSR(req, res)
expect(res.redirect).toBeCalledWith(301, '/redirect')
})
it('catches error and logs before returning 500', () => {
const log = require('llog')
const req = Object.assign({}, mockReq)
const res = Object.assign({}, mockRes)
const getApplicationStream = jest.fn((originalUrl, context) => {
throw new Error('test')
})
const doSSR = ssr(getApplicationStream)
expect(typeof doSSR).toBe('function')
doSSR(req, res)
expect(log.error).toBeCalledWith(Error('test'))
expect(res.status).toBeCalledWith(500)
expect(res.end).toBeCalled()
})
})
describe('defaultSSR', () => {
it('renders app with default SSR', () => {
const req = Object.assign({}, mockReq)
const res = Object.assign({}, mockRes)
defaultSSR(req, res)
expect(res.status).toBeCalledWith(200)
expect(res.write.mock.calls[0][0]).toContain('<!DOCTYPE html>')
expect(res.write.mock.calls[0][0]).toContain(
'window.___REACT_DEFERRED_COMPONENT_MARKS'
)
})
})
describe('#write', () => {
it('write queues data', () => {
const context = {
queue: jest.fn()
}
const buffer = new Buffer.from('hello')
write.call(context, buffer)
expect(context.queue).toBeCalledWith(buffer)
})
})
describe('#end', () => {
it('end queues endingFragment and then null to end stream', () => {
const context = {
queue: jest.fn()
}
const endingFragment = '</html>'
const doEnd = end(endingFragment)
doEnd.call(context)
expect(context.queue).toBeCalledWith(endingFragment)
expect(context.queue).toBeCalledWith(null)
})
})
})
================================================
FILE: app/App.jsx
================================================
import React from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import { lazy, LazyBoundary } from 'react-imported-component'
import { GlobalStyles } from './styles'
import Header from './components/Header'
import Home from './pages/Home'
import LoadingComponent from './pages/Loading'
const About = lazy(() => import('./pages/About'))
export const renderAboutPage = () => (
<LazyBoundary fallback={<LoadingComponent />}>
<About />
</LazyBoundary>
)
const App = () => (
<>
<GlobalStyles />
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/about' render={renderAboutPage} />
<Redirect to='/' />
</Switch>
</>
)
export default App
================================================
FILE: app/client.js
================================================
import React from 'react'
import ReactDOM from 'react-dom'
import { HelmetProvider } from 'react-helmet-async'
import { BrowserRouter } from 'react-router-dom'
import { rehydrateMarks } from 'react-imported-component'
import App from './App'
export const hydrate = (app, element) => () => {
ReactDOM.hydrate(app, element)
}
export const start = ({ isProduction, document, module, hydrate }) => {
const element = document.getElementById('app')
const app = (
<HelmetProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</HelmetProvider>
)
// In production, we want to hydrate instead of render
// because of the server-rendering
if (isProduction) {
// rehydrate the bundle marks from imported-components,
// then rehydrate the react app
rehydrateMarks().then(hydrate(app, element))
} else {
ReactDOM.render(app, element)
}
// Enable Hot Module Reloading
if (module.hot) {
module.hot.accept()
}
}
const options = {
isProduction: process.env.NODE_ENV === 'production',
document: document,
module: module,
hydrate
}
start(options)
================================================
FILE: app/components/Header.jsx
================================================
import React from 'react'
import styled from 'styled-components'
import { NavLink } from 'react-router-dom'
const Header = styled.header`
z-index: 100;
position: fixed;
top: 0;
left: 0;
right: 0;
max-width: 90vw;
margin: 0 auto;
padding: 1em 0;
display: flex;
justify-content: space-between;
align-items: center;
`
const Brand = styled.h1`
font-size: var(--step-up-1);
`
const Menu = styled.ul`
display: flex;
justify-content: flex-end;
align-items: center;
width: 50vw;
`
const MenuLink = styled.li`
margin-left: 2em;
text-decoration: none;
`
export default () => (
<Header>
<Brand>Stream things!</Brand>
<Menu>
<MenuLink>
<NavLink to='/' exact activeClassName='active'>
Home
</NavLink>
</MenuLink>
<MenuLink>
<NavLink to='/about' exact activeClassName='active'>
About
</NavLink>
</MenuLink>
</Menu>
</Header>
)
================================================
FILE: app/components/Page.jsx
================================================
import styled from 'styled-components'
const Page = styled.div`
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
`
export default Page
================================================
FILE: app/imported.js
================================================
/* eslint-disable */
/* tslint:disable */
// generated by react-imported-component, DO NOT EDIT
import {assignImportedComponents} from 'react-imported-component/macro';
// all your imports are defined here
// all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)
// to remove any import from here
// 1) use magic comment `import(/* client-side */ './myFile')` - and it will disappear
// 2) use file filter to ignore specific locations (refer to the README)
const applicationImports = assignImportedComponents([
[() => import('./pages/About'), '', './app/pages/About', false],
]);
export default applicationImports;
// @ts-ignore
if (module.hot) {
// these imports would make this module a parent for the imported modules.
// but this is just a helper - so ignore(and accept!) all updates
// @ts-ignore
module.hot.accept(() => null);
}
================================================
FILE: app/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
</head>
<body>
<div id="app"></div>
<script id="js-entrypoint" src="./client.js"></script>
</body>
</html>
================================================
FILE: app/pages/About.jsx
================================================
import React from 'react'
import { Helmet } from 'react-helmet-async'
import Page from '../components/Page'
const About = () => (
<Page>
<Helmet>
<title>About Page</title>
</Helmet>
<div>This is the about page</div>
</Page>
)
export default About
================================================
FILE: app/pages/Error.jsx
================================================
import React from 'react'
import Page from '../components/Page'
const Error = () => <Page>Error!</Page>
export default Error
================================================
FILE: app/pages/Home.jsx
================================================
import React from 'react'
import { Helmet } from 'react-helmet-async'
import Page from '../components/Page'
const Home = () => (
<Page>
<Helmet>
<title>Home Page</title>
</Helmet>
<div>
Follow me at <a href='https://medium.com/@patrickleet'>@patrickleet</a>
</div>
</Page>
)
export default Home
================================================
FILE: app/pages/Loading.jsx
================================================
import React from 'react'
import Page from '../components/Page'
const Loading = () => <Page>Loading...</Page>
export default Loading
================================================
FILE: app/styles.js
================================================
import { createGlobalStyle } from 'styled-components'
export const GlobalStyles = createGlobalStyle`
/* Base 10 typography scale courtesty of @wesbos 1.6rem === 16px */
html {
font-size: 10px;
}
body {
font-size: 1.6rem;
}
/* Relative Type Scale */
/* https://blog.envylabs.com/responsive-typographic-scales-in-css-b9f60431d1c4 */
:root {
--step-up-5: 2em;
--step-up-4: 1.7511em;
--step-up-3: 1.5157em;
--step-up-2: 1.3195em;
--step-up-1: 1.1487em;
/* baseline: 1em */
--step-down-1: 0.8706em;
--step-down-2: 0.7579em;
--step-down-3: 0.6599em;
--step-down-4: 0.5745em;
--step-down-5: 0.5em;
/* Colors */
--header: rgb(0,0,0);
}
/* https://css-tricks.com/snippets/css/system-font-stack/ */
/* Define the "system" font family */
/* Fastest loading font - the one native to their device */
@font-face {
font-family: system;
font-style: normal;
font-weight: 300;
src: local(".SFNSText-Light"), local(".HelveticaNeueDeskInterface-Light"), local(".LucidaGrandeUI"), local("Ubuntu Light"), local("Segoe UI Light"), local("Roboto-Light"), local("DroidSans"), local("Tahoma");
}
/* Modern CSS Reset */
/* https://alligator.io/css/minimal-css-reset/ */
body, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button {
margin: 0;
padding: 0;
font-weight: normal;
}
body, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button {
font-family: "system"
}
*, *:before, *:after {
box-sizing: inherit;
}
ol, ul {
list-style: none;
}
img {
max-width: 100%;
height: auto;
}
/* Links */
a {
text-decoration: underline;
color: inherit;
&.active {
text-decoration: none;
}
}
`
================================================
FILE: docker-compose.builder.yml
================================================
version: '3.4'
x-base:
&base
image: node:12
volumes:
- nodemodules:/usr/src/service/node_modules
- .:/usr/src/service/
working_dir: /usr/src/service/
services:
install:
<< : *base
command: npm i
build:
<< : *base
command: npm run build
create-bundles:
<< : *base
command: npm run create-bundles
volumes:
nodemodules:
external: true
================================================
FILE: docker-compose.yml
================================================
version: '3'
services:
dev:
image: node:12
volumes:
- nodemodules:/usr/src/service/node_modules
- .:/usr/src/service
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
working_dir: /usr/src/service
command: npm run dev
ports:
- 1234:1234
- 1235:1235
volumes:
nodemodules:
external: true
================================================
FILE: jest.json
================================================
{
"roots": ["<rootDir>/__tests__/unit"],
"modulePaths": [
"<rootDir>",
"/node_modules/"
],
"moduleFileExtensions": [
"js",
"jsx"
],
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"transformIgnorePatterns": ["/node_modules/"],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"collectCoverage": true,
"collectCoverageFrom": [
"**/*.{js,jsx}"
],
"coveragePathIgnorePatterns": [
"<rootDir>/app/imported.js",
"/node_modules/"
],
"setupFilesAfterEnv": ["<rootDir>/__tests__/setup.js"]
}
================================================
FILE: nginx/Dockerfile
================================================
FROM node:12.22.1-alpine AS build
RUN apk add --update --no-cache \
python \
make \
g++ \
git
COPY . /src
WORKDIR /src
RUN npm ci
RUN npm run lint
RUN npm run build:nginx
RUN npm run test
RUN npm prune --production
FROM nginx:1.19.10-alpine
RUN apk add --update --no-cache curl
WORKDIR /usr/src/service
COPY --from=build /src/dist ./dist
COPY --from=build /src/nginx ./nginx
HEALTHCHECK --interval=5s \
--timeout=5s \
--retries=6 \
CMD curl -fs http://localhost:1234/ || exit 1
RUN ["chmod", "+x", "./nginx/entrypoint.sh"]
ENTRYPOINT [ "ash", "./nginx/entrypoint.sh" ]
================================================
FILE: nginx/entrypoint.sh
================================================
#!/bin/bash
# This script can be used when you have webpack or parcel builds that
# insert env variables at build time, usually as build args.
# Just set the build args to an a unique string for replacement,
# and do it post build instead. Uncomment `echo` through `done` and modify
# to match your env variables
# --- Start Insert ENV to JS bundle ---
# echo "Inserting env variables"
# for file in ./dist/**/*.js
# do
# echo "env sub for $file"
# sed -i "s/REPLACE_MIXPANEL_TOKEN/${MIXPANEL_TOKEN}/g" $file
# done
# --- End Insert ENV to JS bundle ---
# And if you need env variables in Nginx, use this instead of `cp`
# --- Start Insert ENV to Nginx---
# echo "Injecting Nginx ENV Vars..."
# envsubst '${GRAPHQL_URL}' < nginx/nginx.conf.template > /etc/nginx/nginx.conf
# --- End Insert ENV to Nginx---
cp nginx/nginx.conf.template /etc/nginx/nginx.conf
echo "Using config:"
cat /etc/nginx/nginx.conf
echo "Starting nginx..."
nginx -c '/etc/nginx/nginx.conf' -g 'daemon off;'
================================================
FILE: nginx/nginx.conf.template
================================================
events {
worker_connections 1024;
}
http {
server {
include /etc/nginx/mime.types;
listen 1234;
root /usr/src/service/dist/client;
index index.html;
gzip on;
gzip_min_length 1000;
gzip_buffers 4 32k;
gzip_proxied any;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_vary on;
location ~* \.(?:css|js|eot|woff|woff2|ttf|svg|otf) {
# Enable GZip for static files
gzip_static on;
# Indefinite caching for static files
expires max;
add_header Cache-Control "public";
}
}
}
================================================
FILE: package.json
================================================
{
"name": "stream-all-the-things",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "npm run generate-imported-components && parcel app/index.html --hmr-port 1235",
"dev:server": "nodemon -e js,jsx,html --ignore dist --ignore app/imported.js --exec \"npm run build && npm run start\"",
"lint": "prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --lint",
"lint:fix": "prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --lint --fix",
"format": "prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --format",
"build": "rimraf dist && npm run generate-imported-components && npm run create-bundles",
"build:nginx": "rimraf dist && npm run generate-imported-components && npm run create-bundle:nginx",
"create-bundles": "concurrently \"npm run create-bundle:client\" \"npm run create-bundle:server\"",
"create-bundle:client": "cross-env BABEL_ENV=client parcel build app/index.html -d dist/client --public-url /dist/client",
"create-bundle:nginx": "cross-env BABEL_ENV=client parcel build app/index.html -d dist/client --public-url .",
"create-bundle:server": "cross-env BABEL_ENV=server parcel build server/index.js -d dist/server --public-url /dist --target=node",
"generate-imported-components": "imported-components app app/imported.js",
"start": "node dist/server",
"test": "cross-env BABEL_ENV=test jest --config jest.json",
"test:watch": "cross-env BABEL_ENV=test jest --config jest.json --watch"
},
"lint-staged": {
"*": [
"prettier-standard --lint",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && npm run test"
}
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"llog": "^0.2.0",
"pino": "^6.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet-async": "^1.0.4",
"react-imported-component": "^6.2.1",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"styled-components": "^5.0.1",
"through": "^2.3.8"
},
"devDependencies": {
"@babel/core": "7.13.15",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/polyfill": "7.12.1",
"@babel/preset-env": "7.13.15",
"@babel/preset-react": "7.13.13",
"babel-jest": "26.6.3",
"concurrently": "5.3.0",
"cross-env": "7.0.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"husky": "4.3.8",
"jest": "26.6.3",
"lint-staged": "10.5.4",
"nodemon": "2.0.7",
"parcel-bundler": "1.12.5",
"prettier-standard": "16.4.1",
"react-hot-loader": "4.13.0",
"rimraf": "3.0.2"
}
}
================================================
FILE: renovate.json
================================================
{
"extends": [
"config:base"
],
"automerge": true,
"major": {
"automerge": false
}
}
================================================
FILE: server/index.js
================================================
import path from 'path'
import log from 'llog'
import { server, serveStatic } from './lib/server'
import ssr from './lib/ssr'
// Expose the public directory as /dist and point to the browser version
server.use(
'/dist/client',
serveStatic(path.resolve(process.cwd(), 'dist', 'client'))
)
// Anything unresolved is serving the application and let
// react-router do the routing!
server.get('/*', ssr)
// Check for PORT environment variable, otherwise fallback on Parcel default port
const port = process.env.PORT || 1234
export const onListen = port => () => {
log.info(`Listening on port ${port}...`)
}
server.listen(port, onListen(port))
================================================
FILE: server/lib/client.js
================================================
import fs from 'fs'
import path from 'path'
const htmlPath = path.join(process.cwd(), 'dist', 'client', 'index.html')
const rawHTML = fs.readFileSync(htmlPath).toString()
const appString = '<div id="app">'
const splitter = '###SPLIT###'
const [startingRawHTMLFragment, endingRawHTMLFragment] = rawHTML
.replace(appString, `${appString}${splitter}`)
.split(splitter)
export const getHTMLFragments = ({ drainHydrateMarks }) => {
const startingHTMLFragment = `${startingRawHTMLFragment}${drainHydrateMarks}`
return [startingHTMLFragment, endingRawHTMLFragment]
}
================================================
FILE: server/lib/server.js
================================================
import express from 'express'
export const server = express()
export const serveStatic = express.static
================================================
FILE: server/lib/ssr.js
================================================
import React from 'react'
import { renderToNodeStream } from 'react-dom/server'
import { HelmetProvider } from 'react-helmet-async'
import { StaticRouter } from 'react-router-dom'
import { ServerStyleSheet } from 'styled-components'
import { printDrainHydrateMarks } from 'react-imported-component'
import log from 'llog'
import through from 'through'
import App from '../../app/App'
import { getHTMLFragments } from './client'
// import { getDataFromTree } from 'react-apollo';
const getApplicationStream = (originalUrl, context) => {
const helmetContext = {}
const app = (
<HelmetProvider context={helmetContext}>
<StaticRouter location={originalUrl} context={context}>
<App />
</StaticRouter>
</HelmetProvider>
)
const sheet = new ServerStyleSheet()
return sheet.interleaveWithNodeStream(
renderToNodeStream(sheet.collectStyles(app))
)
}
export function write (data) {
this.queue(data)
}
export const end = endingHTMLFragment =>
function end () {
this.queue(endingHTMLFragment)
this.queue(null)
}
export const ssr = getApplicationStream => (req, res) => {
try {
// If you were using Apollo, you could fetch data with this
// await getDataFromTree(app);
const context = {}
const stream = getApplicationStream(req.originalUrl, context)
if (context.url) {
return res.redirect(301, context.url)
}
const [startingHTMLFragment, endingHTMLFragment] = getHTMLFragments({
drainHydrateMarks: printDrainHydrateMarks()
})
res.status(200)
res.write(startingHTMLFragment)
stream.pipe(through(write, end(endingHTMLFragment))).pipe(res)
} catch (e) {
log.error(e)
res.status(500)
res.end()
}
}
const defaultSSR = ssr(getApplicationStream)
export default defaultSSR
gitextract_yyu56on6/
├── .babelrc
├── .dockerignore
├── .eslintignore
├── .gitignore
├── .prettierignore
├── .travis.yml
├── Dockerfile
├── Makefile
├── README.md
├── __tests__/
│ ├── setup.js
│ └── unit/
│ ├── app/
│ │ ├── App.jsx
│ │ ├── client.js
│ │ ├── components/
│ │ │ ├── Header.jsx
│ │ │ └── Page.jsx
│ │ ├── pages/
│ │ │ ├── About.jsx
│ │ │ ├── Error.jsx
│ │ │ ├── Home.jsx
│ │ │ └── Loading.jsx
│ │ └── styles.js
│ └── server/
│ ├── index.js
│ └── lib/
│ ├── client.js
│ ├── server.js
│ └── ssr.js
├── app/
│ ├── App.jsx
│ ├── client.js
│ ├── components/
│ │ ├── Header.jsx
│ │ └── Page.jsx
│ ├── imported.js
│ ├── index.html
│ ├── pages/
│ │ ├── About.jsx
│ │ ├── Error.jsx
│ │ ├── Home.jsx
│ │ └── Loading.jsx
│ └── styles.js
├── docker-compose.builder.yml
├── docker-compose.yml
├── jest.json
├── nginx/
│ ├── Dockerfile
│ ├── entrypoint.sh
│ └── nginx.conf.template
├── package.json
├── renovate.json
└── server/
├── index.js
└── lib/
├── client.js
├── server.js
└── ssr.js
SYMBOL INDEX (2 symbols across 2 files)
FILE: __tests__/unit/app/client.js
constant DOM (line 14) | const DOM = new JSDOM(indexHTML)
FILE: server/lib/ssr.js
function write (line 29) | function write (data) {
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
{
"path": ".babelrc",
"chars": 411,
"preview": "{\n \"env\": {\n \"test\": {\n \"presets\":[\n [\"@babel/preset-env\"],\n [\"@babel/preset-react\"],\n ],\n "
},
{
"path": ".dockerignore",
"chars": 33,
"preview": ".cache\ncoverage\ndist\nnode_modules"
},
{
"path": ".eslintignore",
"chars": 42,
"preview": "app/imported.js\ndist\ncoverage\nnode_modules"
},
{
"path": ".gitignore",
"chars": 39,
"preview": "node_modules\n*.log\n.cache\ndist\ncoverage"
},
{
"path": ".prettierignore",
"chars": 42,
"preview": "app/imported.js\ndist\ncoverage\nnode_modules"
},
{
"path": ".travis.yml",
"chars": 360,
"preview": "jobs:\n include:\n - language: node_js\n node_js:\n - 12\n before_script:\n - npm run build\n "
},
{
"path": "Dockerfile",
"chars": 623,
"preview": "FROM node:12.22.1-alpine AS build\n\nRUN apk add --update --no-cache \\\n python \\\n make \\\n g++ \\\n git\n\nWORKDIR "
},
{
"path": "Makefile",
"chars": 163,
"preview": "setup:\n\tdocker volume create nodemodules\ninstall:\n\tdocker-compose\t-f docker-compose.builder.yml run --rm install\ndev:\n\td"
},
{
"path": "README.md",
"chars": 1987,
"preview": "# streaming-ssr-react-styled-components\n\nLearn how this boilerplate was created with my tutorials on HackerNoon!\n\n* [Par"
},
{
"path": "__tests__/setup.js",
"chars": 122,
"preview": "import { configure } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nconfigure({ adapter: new Adapter() }"
},
{
"path": "__tests__/unit/app/App.jsx",
"chars": 390,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport App, { renderAboutPage } from 'app/App.jsx'\n\ndescribe("
},
{
"path": "__tests__/unit/app/client.js",
"chars": 2573,
"preview": "import React from 'react'\nimport fs from 'fs'\nimport path from 'path'\nimport { start, hydrate } from 'app/client'\nimport"
},
{
"path": "__tests__/unit/app/components/Header.jsx",
"chars": 368,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Header from 'app/components/Header.jsx'\n\ndescribe('app"
},
{
"path": "__tests__/unit/app/components/Page.jsx",
"chars": 291,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Page from 'app/components/Page.jsx'\n\ndescribe('app/com"
},
{
"path": "__tests__/unit/app/pages/About.jsx",
"chars": 479,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport About from 'app/pages/About.jsx'\n\ndescribe('app/pages/"
},
{
"path": "__tests__/unit/app/pages/Error.jsx",
"chars": 337,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Error from 'app/pages/Error.jsx'\n\ndescribe('app/pages/"
},
{
"path": "__tests__/unit/app/pages/Home.jsx",
"chars": 584,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Home from 'app/pages/Home.jsx'\n\ndescribe('app/pages/Ho"
},
{
"path": "__tests__/unit/app/pages/Loading.jsx",
"chars": 353,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Loading from 'app/pages/Loading.jsx'\n\ndescribe('app/pa"
},
{
"path": "__tests__/unit/app/styles.js",
"chars": 297,
"preview": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport { GlobalStyles } from 'app/styles.js'\n\ndescribe('app/s"
},
{
"path": "__tests__/unit/server/index.js",
"chars": 808,
"preview": "import { onListen } from 'server/index'\n\njest.mock('llog')\njest.mock('server/lib/server', () => ({\n server: {\n use: "
},
{
"path": "__tests__/unit/server/lib/client.js",
"chars": 380,
"preview": "import { getHTMLFragments } from 'server/lib/client.js'\n\ndescribe('client', () => {\n it('exists', () => {\n const dra"
},
{
"path": "__tests__/unit/server/lib/server.js",
"chars": 381,
"preview": "import express from 'express'\nimport { server, serveStatic } from 'server/lib/server.js'\n\ndescribe('server/lib/server', "
},
{
"path": "__tests__/unit/server/lib/ssr.js",
"chars": 2468,
"preview": "/**\n * @jest-environment node\n */\nimport defaultSSR, { ssr, write, end } from 'server/lib/ssr.js'\n\njest.mock('llog')\n\nco"
},
{
"path": "app/App.jsx",
"chars": 734,
"preview": "import React from 'react'\nimport { Switch, Route, Redirect } from 'react-router-dom'\nimport { lazy, LazyBoundary } from "
},
{
"path": "app/client.js",
"chars": 1110,
"preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { HelmetProvider } from 'react-helmet-async'\nimport { "
},
{
"path": "app/components/Header.jsx",
"chars": 946,
"preview": "import React from 'react'\nimport styled from 'styled-components'\nimport { NavLink } from 'react-router-dom'\n\nconst Heade"
},
{
"path": "app/components/Page.jsx",
"chars": 210,
"preview": "import styled from 'styled-components'\n\nconst Page = styled.div`\n width: 100vw;\n height: 100vh;\n display: flex;\n jus"
},
{
"path": "app/imported.js",
"chars": 1027,
"preview": "\n /* eslint-disable */\n /* tslint:disable */\n \n // generated by react-imported-component, DO NOT EDIT \n "
},
{
"path": "app/index.html",
"chars": 401,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <meta content=\"text/html;charset=utf-8\" http-equiv=\"Con"
},
{
"path": "app/pages/About.jsx",
"chars": 272,
"preview": "import React from 'react'\nimport { Helmet } from 'react-helmet-async'\nimport Page from '../components/Page'\n\nconst About"
},
{
"path": "app/pages/Error.jsx",
"chars": 126,
"preview": "import React from 'react'\nimport Page from '../components/Page'\n\nconst Error = () => <Page>Error!</Page>\nexport default "
},
{
"path": "app/pages/Home.jsx",
"chars": 330,
"preview": "import React from 'react'\nimport { Helmet } from 'react-helmet-async'\nimport Page from '../components/Page'\n\nconst Home "
},
{
"path": "app/pages/Loading.jsx",
"chars": 134,
"preview": "import React from 'react'\nimport Page from '../components/Page'\n\nconst Loading = () => <Page>Loading...</Page>\nexport de"
},
{
"path": "app/styles.js",
"chars": 1679,
"preview": "import { createGlobalStyle } from 'styled-components'\n\nexport const GlobalStyles = createGlobalStyle`\n/* Base 10 typogra"
},
{
"path": "docker-compose.builder.yml",
"chars": 389,
"preview": "version: '3.4'\n\nx-base:\n &base\n image: node:12\n volumes:\n - nodemodules:/usr/src/service/node_modules\n - .:/usr"
},
{
"path": "docker-compose.yml",
"chars": 368,
"preview": "version: '3'\nservices:\n dev:\n image: node:12\n volumes:\n - nodemodules:/usr/src/service/node_modules\n - "
},
{
"path": "jest.json",
"chars": 634,
"preview": "{\n \"roots\": [\"<rootDir>/__tests__/unit\"],\n \"modulePaths\": [\n \"<rootDir>\",\n \"/node_modules/\"\n ],\n \"moduleFileEx"
},
{
"path": "nginx/Dockerfile",
"chars": 631,
"preview": "FROM node:12.22.1-alpine AS build\n\nRUN apk add --update --no-cache \\\n python \\\n make \\\n g++ \\\n git\n\nCOPY . /"
},
{
"path": "nginx/entrypoint.sh",
"chars": 989,
"preview": "#!/bin/bash\n\n\n# This script can be used when you have webpack or parcel builds that \n# insert env variables at build tim"
},
{
"path": "nginx/nginx.conf.template",
"chars": 623,
"preview": "events {\n worker_connections 1024;\n}\n\nhttp {\n server {\n include /etc/nginx/mime.types;\n listen 1234;\n\n root "
},
{
"path": "package.json",
"chars": 2696,
"preview": "{\n \"name\": \"stream-all-the-things\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n "
},
{
"path": "renovate.json",
"chars": 103,
"preview": "{\n \"extends\": [\n \"config:base\"\n ],\n \"automerge\": true,\n \"major\": {\n \"automerge\": false\n }\n}\n"
},
{
"path": "server/index.js",
"chars": 648,
"preview": "import path from 'path'\nimport log from 'llog'\nimport { server, serveStatic } from './lib/server'\nimport ssr from './lib"
},
{
"path": "server/lib/client.js",
"chars": 571,
"preview": "import fs from 'fs'\nimport path from 'path'\n\nconst htmlPath = path.join(process.cwd(), 'dist', 'client', 'index.html')\nc"
},
{
"path": "server/lib/server.js",
"chars": 105,
"preview": "import express from 'express'\n\nexport const server = express()\nexport const serveStatic = express.static\n"
},
{
"path": "server/lib/ssr.js",
"chars": 1795,
"preview": "import React from 'react'\nimport { renderToNodeStream } from 'react-dom/server'\nimport { HelmetProvider } from 'react-he"
}
]
About this extraction
This page contains the full source code of the patrickleet/streaming-ssr-react-styled-components GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (29.3 KB), approximately 9.2k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.