[
  {
    "path": ".babelrc",
    "content": "{\n  \"env\": {\n    \"test\": {\n      \"presets\":[\n        [\"@babel/preset-env\"],\n        [\"@babel/preset-react\"],\n      ],\n      \"plugins\": [\n        [\"@babel/plugin-syntax-dynamic-import\"]\n      ]\n    },\n    \"server\": {\n      \"plugins\": [\"react-imported-component/babel\", \"@babel/plugin-syntax-dynamic-import\"]\n    },\n    \"client\": {\n      \"plugins\": [\n        [\"react-imported-component/babel\"]\n      ]\n    }\n  }\n}"
  },
  {
    "path": ".dockerignore",
    "content": ".cache\ncoverage\ndist\nnode_modules"
  },
  {
    "path": ".eslintignore",
    "content": "app/imported.js\ndist\ncoverage\nnode_modules"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n*.log\n.cache\ndist\ncoverage"
  },
  {
    "path": ".prettierignore",
    "content": "app/imported.js\ndist\ncoverage\nnode_modules"
  },
  {
    "path": ".travis.yml",
    "content": "jobs:\n  include:\n    - language: node_js\n      node_js:\n        - 12\n      before_script:\n        - npm run build\n      after_success:\n        - npm i -g codecov\n        - codecov\n    - services:\n        - docker\n      script:\n        - docker build . -t ssr\n    - services:\n        - docker\n      script:\n        - docker build . -f nginx/Dockerfile -t nginx\n"
  },
  {
    "path": "Dockerfile",
    "content": "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 /src\nCOPY ./package* ./\n\nRUN npm ci\n\nCOPY . .\n\nRUN npm run lint\nRUN npm run build\nRUN npm run test\n\nRUN npm prune --production\n\nFROM node:12.22.1-alpine\n\nRUN apk add --update --no-cache curl\n\nEXPOSE 1234\n\nWORKDIR /usr/src/service\n\nCOPY --from=build /src/node_modules node_modules\nCOPY --from=build /src/dist dist\n\nHEALTHCHECK --interval=5s \\\n            --timeout=5s \\\n            --retries=6 \\\n            CMD curl -fs http://localhost:1234/ || exit 1\n\nUSER node\n\nCMD [\"node\", \"./dist/server/index.js\"]"
  },
  {
    "path": "Makefile",
    "content": "setup:\n\tdocker volume create nodemodules\ninstall:\n\tdocker-compose\t-f docker-compose.builder.yml run --rm install\ndev:\n\tdocker-compose up\ndown:\n\tdocker-compose down"
  },
  {
    "path": "README.md",
    "content": "# streaming-ssr-react-styled-components\n\nLearn how this boilerplate was created with my tutorials on HackerNoon!\n\n* [Part 1: Move over Next.js and Webpack!!](https://hackernoon.com/move-over-next-js-and-webpack-ba367f07545)\n* [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)\n* [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)\n* [Part 4: The 100% Code Coverage Myth](https://hackernoon.com/the-100-code-coverage-myth-900b83d20d3d)\n* [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)\n* [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)\n\nUPDATE: 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?\n\nJust in case...\n\nYou can find the original versions of the articles on Medium with the original formatting at the following links:\n\n* https://medium.com/hackernoon/move-over-next-js-and-webpack-ba367f07545\n* https://medium.com/hackernoon/a-better-way-to-develop-node-js-with-docker-cd29d3a0093\n* https://medium.com/hackernoon/enforcing-code-quality-for-node-js-c3b837d7ae17\n* https://medium.com/hackernoon/the-100-code-coverage-myth-900b83d20d3d\n* https://medium.com/hackernoon/a-tale-of-two-docker-multi-stage-build-layers-85348a409c84\n\nSorry for the inconvenience in tracking these down!\n\n## tl;dr;\n\n### Developing with Docker\n\n```\n# setup only needs to be run once\nmake setup\n\nmake install\nmake dev\n```\n\n### Developing Locally\n\n```\nnpm i\nnpm run dev\n```\n"
  },
  {
    "path": "__tests__/setup.js",
    "content": "import { configure } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nconfigure({ adapter: new Adapter() });"
  },
  {
    "path": "__tests__/unit/app/App.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport App, { renderAboutPage } from 'app/App.jsx'\n\ndescribe('app/App.jsx', () => {\n  it('renders App', () => {\n    expect(App).toBeDefined()\n    const tree = shallow(<App />)\n    expect(tree).not.toBeNull()\n  })\n\n  it('#renderAboutPage', () => {\n    const tree = shallow(renderAboutPage())\n    expect(tree).not.toBeNull()\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/client.js",
    "content": "import React from 'react'\nimport fs from 'fs'\nimport path from 'path'\nimport { start, hydrate } from 'app/client'\nimport { JSDOM } from 'jsdom'\n\njest.mock('react-dom')\njest.mock('react-imported-component')\njest.mock('app/imported.js')\n\n// mock DOM with actual index.html contents\nconst pathToIndex = path.join(process.cwd(), 'app', 'index.html')\nconst indexHTML = fs.readFileSync(pathToIndex).toString()\nconst DOM = new JSDOM(indexHTML)\nconst document = DOM.window.document\n\n// this doesn't contribute to coverage, but we\n// should know if it changes as it would\n// cause our app to break\ndescribe('app/index.html', () => {\n  it('has element with id \"app\"', () => {\n    const element = document.getElementById('app')\n    expect(element.id).toBe('app')\n  })\n})\n\ndescribe('app/client.js', () => {\n  // Reset counts of mock calls after each test\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  describe('#start', () => {\n    it('renders when in development and accepts hot module reloads', () => {\n      // this is mocked above, so require gets the mock version\n      // so we can see if its functions are called\n      const ReactDOM = require('react-dom')\n\n      // mock module.hot\n      const module = {\n        hot: {\n          accept: jest.fn()\n        }\n      }\n\n      // mock options\n      const options = {\n        isProduction: false,\n        module,\n        document\n      }\n\n      start(options)\n      expect(ReactDOM.render).toBeCalled()\n      expect(module.hot.accept).toBeCalled()\n    })\n\n    it('hydrates when in production does not accept hot module reloads', () => {\n      const ReactDOM = require('react-dom')\n      const importedComponent = require('react-imported-component')\n      importedComponent.rehydrateMarks.mockImplementation(() =>\n        Promise.resolve()\n      )\n\n      // mock module.hot\n      const module = {}\n\n      // mock rehydrate function\n      const hydrate = jest.fn()\n\n      // mock options\n      const options = {\n        isProduction: true,\n        module,\n        document,\n        hydrate\n      }\n\n      start(options)\n      expect(ReactDOM.render).not.toBeCalled()\n      expect(hydrate).toBeCalled()\n    })\n  })\n\n  describe('#hydrate', () => {\n    it('uses ReactDOM to hydrate given element with an app', () => {\n      const ReactDOM = require('react-dom')\n      const element = document.getElementById('app')\n      const app = <div />\n      const doHydrate = hydrate(app, element)\n\n      expect(typeof doHydrate).toBe('function')\n\n      doHydrate()\n      expect(ReactDOM.hydrate).toBeCalledWith(app, element)\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/components/Header.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Header from 'app/components/Header.jsx'\n\ndescribe('app/components/Header.jsx', () => {\n  it('renders Header component', () => {\n    expect(Header).toBeDefined()\n    const tree = shallow(<Header />)\n    expect(tree.find('Page')).toBeDefined()\n    expect(tree.text()).toContain('Stream things!')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/components/Page.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Page from 'app/components/Page.jsx'\n\ndescribe('app/components/Page.jsx', () => {\n  it('renders Page component', () => {\n    expect(Page).toBeDefined()\n    const tree = shallow(<Page />)\n    expect(tree).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/pages/About.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport About from 'app/pages/About.jsx'\n\ndescribe('app/pages/About.jsx', () => {\n  it('renders About page', () => {\n    expect(About).toBeDefined()\n    const tree = shallow(<About />)\n    expect(tree.find('Page')).toBeDefined()\n    expect(\n      tree\n        .find('Helmet')\n        .find('title')\n        .text()\n    ).toEqual('About Page')\n    expect(tree.find('div').text()).toEqual('This is the about page')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/pages/Error.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Error from 'app/pages/Error.jsx'\n\ndescribe('app/pages/Error.jsx', () => {\n  it('renders Error page', () => {\n    expect(Error).toBeDefined()\n    const tree = shallow(<Error />)\n    expect(tree.find('Page')).toBeDefined()\n    expect(tree.text()).toEqual('Error!')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/pages/Home.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Home from 'app/pages/Home.jsx'\n\ndescribe('app/pages/Home.jsx', () => {\n  it('renders Home page', () => {\n    expect(Home).toBeDefined()\n    const tree = shallow(<Home />)\n    expect(tree.find('Page')).toBeDefined()\n    expect(\n      tree\n        .find('Helmet')\n        .find('title')\n        .text()\n    ).toEqual('Home Page')\n    expect(tree.find('div').text()).toEqual('Follow me at @patrickleet')\n    expect(\n      tree\n        .find('div')\n        .find('a')\n        .text()\n    ).toEqual('@patrickleet')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/pages/Loading.jsx",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport Loading from 'app/pages/Loading.jsx'\n\ndescribe('app/pages/Loading.jsx', () => {\n  it('renders Loading page', () => {\n    expect(Loading).toBeDefined()\n    const tree = shallow(<Loading />)\n    expect(tree.find('Page')).toBeDefined()\n    expect(tree.text()).toEqual('Loading...')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/app/styles.js",
    "content": "import React from 'react'\nimport { shallow } from 'enzyme'\nimport { GlobalStyles } from 'app/styles.js'\n\ndescribe('app/styles.js', () => {\n  it('renders styles page', () => {\n    expect(GlobalStyles).toBeDefined()\n    const tree = shallow(<GlobalStyles />)\n    expect(tree).not.toBeNull()\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/server/index.js",
    "content": "import { onListen } from 'server/index'\n\njest.mock('llog')\njest.mock('server/lib/server', () => ({\n  server: {\n    use: jest.fn(),\n    get: jest.fn(),\n    listen: jest.fn()\n  },\n  serveStatic: jest.fn(() => \"static/path\")\n}))\njest.mock('server/lib/ssr')\n\ndescribe('server/index.js', () => {\n  it('main', () => {\n    const { server, serveStatic } = require('server/lib/server')\n    expect(server.use).toBeCalledWith('/dist/client', \"static/path\")\n    expect(serveStatic).toBeCalledWith(`${process.cwd()}/dist/client`)\n    expect(server.get).toBeCalledWith('/*', expect.any(Function))\n    expect(server.listen).toBeCalledWith(1234, expect.any(Function))\n  })\n\n  it('onListen', () => {\n    const log = require('llog')\n    onListen(4000)()\n    expect(log.info).toBeCalledWith('Listening on port 4000...')\n  })\n})"
  },
  {
    "path": "__tests__/unit/server/lib/client.js",
    "content": "import { getHTMLFragments } from 'server/lib/client.js'\n\ndescribe('client', () => {\n  it('exists', () => {\n    const drainHydrateMarks = '<!-- mock hydrate marks -->'\n    const [start, end] = getHTMLFragments({ drainHydrateMarks })\n    expect(start).toContain('<head>')\n    expect(start).toContain(drainHydrateMarks)\n    expect(end).toContain('script id=\"js-entrypoint\"')\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/server/lib/server.js",
    "content": "import express from 'express'\nimport { server, serveStatic } from 'server/lib/server.js'\n\ndescribe('server/lib/server', () => {\n  it('should provide server APIs to use', () => {\n    expect(server).toBeDefined()\n    expect(server.use).toBeDefined()\n    expect(server.get).toBeDefined()\n    expect(server.listen).toBeDefined()\n    expect(serveStatic).toEqual(express.static)\n  })\n})\n"
  },
  {
    "path": "__tests__/unit/server/lib/ssr.js",
    "content": "/**\n * @jest-environment node\n */\nimport defaultSSR, { ssr, write, end } from 'server/lib/ssr.js'\n\njest.mock('llog')\n\nconst mockReq = {\n  originalUrl: '/'\n}\n\nconst mockRes = {\n  redirect: jest.fn(),\n  status: jest.fn(),\n  end: jest.fn(),\n  write: jest.fn(),\n  on: jest.fn(),\n  removeListener: jest.fn(),\n  emit: jest.fn()\n}\n\ndescribe('server/lib/ssr.js', () => {\n  describe('ssr', () => {\n    it('redirects when context.url is set', () => {\n      const req = Object.assign({}, mockReq)\n      const res = Object.assign({}, mockRes)\n      const getApplicationStream = jest.fn((originalUrl, context) => {\n        context.url = '/redirect'\n      })\n      const doSSR = ssr(getApplicationStream)\n\n      expect(typeof doSSR).toBe('function')\n      doSSR(req, res)\n      expect(res.redirect).toBeCalledWith(301, '/redirect')\n    })\n\n    it('catches error and logs before returning 500', () => {\n      const log = require('llog')\n      const req = Object.assign({}, mockReq)\n      const res = Object.assign({}, mockRes)\n      const getApplicationStream = jest.fn((originalUrl, context) => {\n        throw new Error('test')\n      })\n      const doSSR = ssr(getApplicationStream)\n      expect(typeof doSSR).toBe('function')\n      doSSR(req, res)\n      expect(log.error).toBeCalledWith(Error('test'))\n      expect(res.status).toBeCalledWith(500)\n      expect(res.end).toBeCalled()\n    })\n  })\n  describe('defaultSSR', () => {\n    it('renders app with default SSR', () => {\n      const req = Object.assign({}, mockReq)\n      const res = Object.assign({}, mockRes)\n      defaultSSR(req, res)\n      expect(res.status).toBeCalledWith(200)\n      expect(res.write.mock.calls[0][0]).toContain('<!DOCTYPE html>')\n      expect(res.write.mock.calls[0][0]).toContain(\n        'window.___REACT_DEFERRED_COMPONENT_MARKS'\n      )\n    })\n  })\n\n  describe('#write', () => {\n    it('write queues data', () => {\n      const context = {\n        queue: jest.fn()\n      }\n      const buffer = new Buffer.from('hello')\n      write.call(context, buffer)\n      expect(context.queue).toBeCalledWith(buffer)\n    })\n  })\n\n  describe('#end', () => {\n    it('end queues endingFragment and then null to end stream', () => {\n      const context = {\n        queue: jest.fn()\n      }\n      const endingFragment = '</html>'\n      const doEnd = end(endingFragment)\n      doEnd.call(context)\n      expect(context.queue).toBeCalledWith(endingFragment)\n      expect(context.queue).toBeCalledWith(null)\n    })\n  })\n})\n"
  },
  {
    "path": "app/App.jsx",
    "content": "import React from 'react'\nimport { Switch, Route, Redirect } from 'react-router-dom'\nimport { lazy, LazyBoundary } from 'react-imported-component'\nimport { GlobalStyles } from './styles'\nimport Header from './components/Header'\nimport Home from './pages/Home'\nimport LoadingComponent from './pages/Loading'\n\nconst About = lazy(() => import('./pages/About'))\n\nexport const renderAboutPage = () => (\n  <LazyBoundary fallback={<LoadingComponent />}>\n    <About />\n  </LazyBoundary>\n)\n\nconst App = () => (\n  <>\n    <GlobalStyles />\n    <Header />\n\n    <Switch>\n      <Route exact path='/' component={Home} />\n      <Route exact path='/about' render={renderAboutPage} />\n      <Redirect to='/' />\n    </Switch>\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "app/client.js",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { HelmetProvider } from 'react-helmet-async'\nimport { BrowserRouter } from 'react-router-dom'\nimport { rehydrateMarks } from 'react-imported-component'\nimport App from './App'\n\nexport const hydrate = (app, element) => () => {\n  ReactDOM.hydrate(app, element)\n}\n\nexport const start = ({ isProduction, document, module, hydrate }) => {\n  const element = document.getElementById('app')\n  const app = (\n    <HelmetProvider>\n      <BrowserRouter>\n        <App />\n      </BrowserRouter>\n    </HelmetProvider>\n  )\n\n  // In production, we want to hydrate instead of render\n  // because of the server-rendering\n  if (isProduction) {\n    // rehydrate the bundle marks from imported-components,\n    // then rehydrate the react app\n    rehydrateMarks().then(hydrate(app, element))\n  } else {\n    ReactDOM.render(app, element)\n  }\n\n  // Enable Hot Module Reloading\n  if (module.hot) {\n    module.hot.accept()\n  }\n}\n\nconst options = {\n  isProduction: process.env.NODE_ENV === 'production',\n  document: document,\n  module: module,\n  hydrate\n}\n\nstart(options)\n"
  },
  {
    "path": "app/components/Header.jsx",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { NavLink } from 'react-router-dom'\n\nconst Header = styled.header`\n  z-index: 100;\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  max-width: 90vw;\n  margin: 0 auto;\n  padding: 1em 0;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n`\n\nconst Brand = styled.h1`\n  font-size: var(--step-up-1);\n`\n\nconst Menu = styled.ul`\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  width: 50vw;\n`\n\nconst MenuLink = styled.li`\n  margin-left: 2em;\n  text-decoration: none;\n`\n\nexport default () => (\n  <Header>\n    <Brand>Stream things!</Brand>\n    <Menu>\n      <MenuLink>\n        <NavLink to='/' exact activeClassName='active'>\n          Home\n        </NavLink>\n      </MenuLink>\n      <MenuLink>\n        <NavLink to='/about' exact activeClassName='active'>\n          About\n        </NavLink>\n      </MenuLink>\n    </Menu>\n  </Header>\n)\n"
  },
  {
    "path": "app/components/Page.jsx",
    "content": "import styled from 'styled-components'\n\nconst Page = styled.div`\n  width: 100vw;\n  height: 100vh;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n`\n\nexport default Page\n"
  },
  {
    "path": "app/imported.js",
    "content": "\n    /* eslint-disable */\n    /* tslint:disable */\n    \n    // generated by react-imported-component, DO NOT EDIT     \n    import {assignImportedComponents} from 'react-imported-component/macro';    \n    \n    // all your imports are defined here\n    // all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)\n    // to remove any import from here\n    // 1) use magic comment `import(/* client-side */ './myFile')` - and it will disappear\n    // 2) use file filter to ignore specific locations (refer to the README)\n    \n    const applicationImports = assignImportedComponents([\n           [() => import('./pages/About'), '', './app/pages/About', false],\n    ]);\n    \n    export default applicationImports;\n    \n    // @ts-ignore\n    if (module.hot) {\n       // these imports would make this module a parent for the imported modules.\n       // but this is just a helper - so ignore(and accept!) all updates\n       \n       // @ts-ignore\n       module.hot.accept(() => null);\n    }    \n    "
  },
  {
    "path": "app/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\" />\n    <meta content=\"utf-8\" http-equiv=\"encoding\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n  </head>\n  \n  <body>\n    <div id=\"app\"></div>\n    <script id=\"js-entrypoint\" src=\"./client.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "app/pages/About.jsx",
    "content": "import React from 'react'\nimport { Helmet } from 'react-helmet-async'\nimport Page from '../components/Page'\n\nconst About = () => (\n  <Page>\n    <Helmet>\n      <title>About Page</title>\n    </Helmet>\n\n    <div>This is the about page</div>\n  </Page>\n)\n\nexport default About\n"
  },
  {
    "path": "app/pages/Error.jsx",
    "content": "import React from 'react'\nimport Page from '../components/Page'\n\nconst Error = () => <Page>Error!</Page>\nexport default Error\n"
  },
  {
    "path": "app/pages/Home.jsx",
    "content": "import React from 'react'\nimport { Helmet } from 'react-helmet-async'\nimport Page from '../components/Page'\n\nconst Home = () => (\n  <Page>\n    <Helmet>\n      <title>Home Page</title>\n    </Helmet>\n\n    <div>\n      Follow me at <a href='https://medium.com/@patrickleet'>@patrickleet</a>\n    </div>\n  </Page>\n)\n\nexport default Home\n"
  },
  {
    "path": "app/pages/Loading.jsx",
    "content": "import React from 'react'\nimport Page from '../components/Page'\n\nconst Loading = () => <Page>Loading...</Page>\nexport default Loading\n"
  },
  {
    "path": "app/styles.js",
    "content": "import { createGlobalStyle } from 'styled-components'\n\nexport const GlobalStyles = createGlobalStyle`\n/* Base 10 typography scale courtesty of @wesbos 1.6rem === 16px */\nhtml {\n  font-size: 10px;\n}\n\nbody {\n  font-size: 1.6rem;\n}\n\n/* Relative Type Scale */\n/* https://blog.envylabs.com/responsive-typographic-scales-in-css-b9f60431d1c4 */\n:root {\n  --step-up-5: 2em;\n  --step-up-4: 1.7511em;\n  --step-up-3: 1.5157em;\n  --step-up-2: 1.3195em;\n  --step-up-1: 1.1487em;\n  /* baseline: 1em */\n  --step-down-1: 0.8706em;\n  --step-down-2: 0.7579em;\n  --step-down-3: 0.6599em;\n  --step-down-4: 0.5745em;\n  --step-down-5: 0.5em;\n  /* Colors */\n  --header: rgb(0,0,0);\n}\n\n/* https://css-tricks.com/snippets/css/system-font-stack/ */\n/* Define the \"system\" font family */\n/* Fastest loading font - the one native to their device */\n@font-face {\n  font-family: system;\n  font-style: normal;\n  font-weight: 300;\n  src: local(\".SFNSText-Light\"), local(\".HelveticaNeueDeskInterface-Light\"), local(\".LucidaGrandeUI\"), local(\"Ubuntu Light\"), local(\"Segoe UI Light\"), local(\"Roboto-Light\"), local(\"DroidSans\"), local(\"Tahoma\");\n}\n\n/* Modern CSS Reset */\n/* https://alligator.io/css/minimal-css-reset/ */\nbody, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button {\n  margin: 0;\n  padding: 0;\n  font-weight: normal;\n}\n\nbody, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button {\n  font-family: \"system\"\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nol, ul {\n  list-style: none;\n}\n\nimg {\n  max-width: 100%;\n  height: auto;\n}\n\n/* Links */\na {\n  text-decoration: underline;\n  color: inherit;\n\n  &.active {\n    text-decoration: none;\n  }\n}\n\n`\n"
  },
  {
    "path": "docker-compose.builder.yml",
    "content": "version: '3.4'\n\nx-base:\n  &base\n  image: node:12\n  volumes:\n    - nodemodules:/usr/src/service/node_modules\n    - .:/usr/src/service/\n  working_dir: /usr/src/service/\n\nservices:\n  install:\n    << : *base\n    command: npm i\n\n  build:\n    << : *base\n    command: npm run build\n\n  create-bundles:\n    << : *base\n    command: npm run create-bundles\n\nvolumes:\n  nodemodules:\n    external: true\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3'\nservices:\n  dev:\n    image: node:12\n    volumes:\n      - nodemodules:/usr/src/service/node_modules\n      - .:/usr/src/service\n    environment:\n      - NODE_ENV=development\n      - CHOKIDAR_USEPOLLING=true\n    working_dir: /usr/src/service\n    command: npm run dev\n    ports:\n      - 1234:1234\n      - 1235:1235\n\nvolumes:\n  nodemodules:\n    external: true\n"
  },
  {
    "path": "jest.json",
    "content": "{\n  \"roots\": [\"<rootDir>/__tests__/unit\"],\n  \"modulePaths\": [\n    \"<rootDir>\",\n    \"/node_modules/\"\n  ],\n  \"moduleFileExtensions\": [\n    \"js\",\n    \"jsx\"\n  ],\n  \"transform\": {\n    \"^.+\\\\.jsx?$\": \"babel-jest\"\n  },\n  \"transformIgnorePatterns\": [\"/node_modules/\"],\n  \"coverageThreshold\": {\n    \"global\": {\n      \"branches\": 100,\n      \"functions\": 100,\n      \"lines\": 100,\n      \"statements\": 100\n    }\n  },\n  \"collectCoverage\": true,\n  \"collectCoverageFrom\": [\n    \"**/*.{js,jsx}\"\n  ],\n  \"coveragePathIgnorePatterns\": [\n    \"<rootDir>/app/imported.js\",\n    \"/node_modules/\"\n  ],\n  \"setupFilesAfterEnv\": [\"<rootDir>/__tests__/setup.js\"]\n}"
  },
  {
    "path": "nginx/Dockerfile",
    "content": "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 . /src\nWORKDIR /src\n\nRUN npm ci\n\nRUN npm run lint\nRUN npm run build:nginx\nRUN npm run test\n\nRUN npm prune --production\n\nFROM nginx:1.19.10-alpine\n\nRUN apk add --update --no-cache curl\n\nWORKDIR /usr/src/service\n\nCOPY --from=build /src/dist ./dist\nCOPY --from=build /src/nginx ./nginx\n\nHEALTHCHECK --interval=5s \\\n            --timeout=5s \\\n            --retries=6 \\\n            CMD curl -fs http://localhost:1234/ || exit 1\n\nRUN [\"chmod\", \"+x\", \"./nginx/entrypoint.sh\"]\nENTRYPOINT [ \"ash\", \"./nginx/entrypoint.sh\" ]"
  },
  {
    "path": "nginx/entrypoint.sh",
    "content": "#!/bin/bash\n\n\n# This script can be used when you have webpack or parcel builds that \n# insert env variables at build time, usually as build args. \n# Just set the build args to an a unique string for replacement,\n# and do it post build instead. Uncomment `echo` through `done` and modify\n# to match your env variables\n# --- Start Insert ENV to JS bundle ---\n# echo \"Inserting env variables\"\n# for file in ./dist/**/*.js\n# do\n#   echo \"env sub for $file\"\n#   sed -i \"s/REPLACE_MIXPANEL_TOKEN/${MIXPANEL_TOKEN}/g\" $file\n# done\n# --- End Insert ENV to JS bundle ---\n\n# And if you need env variables in Nginx, use this instead of `cp`\n# --- Start Insert ENV to Nginx---\n# echo \"Injecting Nginx ENV Vars...\"\n# envsubst '${GRAPHQL_URL}' < nginx/nginx.conf.template > /etc/nginx/nginx.conf\n# --- End Insert ENV to Nginx---\ncp nginx/nginx.conf.template /etc/nginx/nginx.conf\n\necho \"Using config:\"\ncat /etc/nginx/nginx.conf\n\necho \"Starting nginx...\"\nnginx -c '/etc/nginx/nginx.conf' -g 'daemon off;'"
  },
  {
    "path": "nginx/nginx.conf.template",
    "content": "events {\n  worker_connections 1024;\n}\n\nhttp {\n  server {\n    include /etc/nginx/mime.types;\n    listen 1234;\n\n    root   /usr/src/service/dist/client;\n    index  index.html;\n\n    gzip on;\n    gzip_min_length 1000;\n    gzip_buffers 4 32k;\n    gzip_proxied any;\n    gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;\n    gzip_vary on;\n\n    location ~* \\.(?:css|js|eot|woff|woff2|ttf|svg|otf) {\n      # Enable GZip for static files\n      gzip_static on;\n\n      # Indefinite caching for static files\n      expires max;\n\n      add_header Cache-Control \"public\";\n    }\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"stream-all-the-things\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"npm run generate-imported-components && parcel app/index.html --hmr-port 1235\",\n    \"dev:server\": \"nodemon -e js,jsx,html --ignore dist --ignore app/imported.js --exec \\\"npm run build && npm run start\\\"\",\n    \"lint\": \"prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --lint\",\n    \"lint:fix\": \"prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --lint --fix\",\n    \"format\": \"prettier-standard 'app/**/*.js' 'app/**/*.jsx' 'server/**/*.js' --format\",\n    \"build\": \"rimraf dist && npm run generate-imported-components && npm run create-bundles\",\n    \"build:nginx\": \"rimraf dist && npm run generate-imported-components && npm run create-bundle:nginx\",\n    \"create-bundles\": \"concurrently \\\"npm run create-bundle:client\\\" \\\"npm run create-bundle:server\\\"\",\n    \"create-bundle:client\": \"cross-env BABEL_ENV=client parcel build app/index.html -d dist/client --public-url /dist/client\",\n    \"create-bundle:nginx\": \"cross-env BABEL_ENV=client parcel build app/index.html -d dist/client --public-url .\",\n    \"create-bundle:server\": \"cross-env BABEL_ENV=server parcel build server/index.js -d dist/server --public-url /dist --target=node\",\n    \"generate-imported-components\": \"imported-components app app/imported.js\",\n    \"start\": \"node dist/server\",\n    \"test\": \"cross-env BABEL_ENV=test jest --config jest.json\",\n    \"test:watch\": \"cross-env BABEL_ENV=test jest --config jest.json --watch\"\n  },\n  \"lint-staged\": {\n    \"*\": [\n      \"prettier-standard --lint\",\n      \"git add\"\n    ]\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged && npm run test\"\n    }\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"express\": \"^4.17.1\",\n    \"llog\": \"^0.2.0\",\n    \"pino\": \"^6.0.0\",\n    \"react\": \"^16.12.0\",\n    \"react-dom\": \"^16.12.0\",\n    \"react-helmet-async\": \"^1.0.4\",\n    \"react-imported-component\": \"^6.2.1\",\n    \"react-router\": \"^5.1.2\",\n    \"react-router-dom\": \"^5.1.2\",\n    \"styled-components\": \"^5.0.1\",\n    \"through\": \"^2.3.8\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.13.15\",\n    \"@babel/plugin-syntax-dynamic-import\": \"7.8.3\",\n    \"@babel/polyfill\": \"7.12.1\",\n    \"@babel/preset-env\": \"7.13.15\",\n    \"@babel/preset-react\": \"7.13.13\",\n    \"babel-jest\": \"26.6.3\",\n    \"concurrently\": \"5.3.0\",\n    \"cross-env\": \"7.0.3\",\n    \"enzyme\": \"3.11.0\",\n    \"enzyme-adapter-react-16\": \"1.15.6\",\n    \"husky\": \"4.3.8\",\n    \"jest\": \"26.6.3\",\n    \"lint-staged\": \"10.5.4\",\n    \"nodemon\": \"2.0.7\",\n    \"parcel-bundler\": \"1.12.5\",\n    \"prettier-standard\": \"16.4.1\",\n    \"react-hot-loader\": \"4.13.0\",\n    \"rimraf\": \"3.0.2\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"automerge\": true,\n  \"major\": {\n    \"automerge\": false\n  }\n}\n"
  },
  {
    "path": "server/index.js",
    "content": "import path from 'path'\nimport log from 'llog'\nimport { server, serveStatic } from './lib/server'\nimport ssr from './lib/ssr'\n\n// Expose the public directory as /dist and point to the browser version\nserver.use(\n  '/dist/client',\n  serveStatic(path.resolve(process.cwd(), 'dist', 'client'))\n)\n\n// Anything unresolved is serving the application and let\n// react-router do the routing!\nserver.get('/*', ssr)\n\n// Check for PORT environment variable, otherwise fallback on Parcel default port\nconst port = process.env.PORT || 1234\nexport const onListen = port => () => {\n  log.info(`Listening on port ${port}...`)\n}\nserver.listen(port, onListen(port))\n"
  },
  {
    "path": "server/lib/client.js",
    "content": "import fs from 'fs'\nimport path from 'path'\n\nconst htmlPath = path.join(process.cwd(), 'dist', 'client', 'index.html')\nconst rawHTML = fs.readFileSync(htmlPath).toString()\n\nconst appString = '<div id=\"app\">'\nconst splitter = '###SPLIT###'\nconst [startingRawHTMLFragment, endingRawHTMLFragment] = rawHTML\n  .replace(appString, `${appString}${splitter}`)\n  .split(splitter)\n\nexport const getHTMLFragments = ({ drainHydrateMarks }) => {\n  const startingHTMLFragment = `${startingRawHTMLFragment}${drainHydrateMarks}`\n  return [startingHTMLFragment, endingRawHTMLFragment]\n}\n"
  },
  {
    "path": "server/lib/server.js",
    "content": "import express from 'express'\n\nexport const server = express()\nexport const serveStatic = express.static\n"
  },
  {
    "path": "server/lib/ssr.js",
    "content": "import React from 'react'\nimport { renderToNodeStream } from 'react-dom/server'\nimport { HelmetProvider } from 'react-helmet-async'\nimport { StaticRouter } from 'react-router-dom'\nimport { ServerStyleSheet } from 'styled-components'\nimport { printDrainHydrateMarks } from 'react-imported-component'\nimport log from 'llog'\nimport through from 'through'\nimport App from '../../app/App'\nimport { getHTMLFragments } from './client'\n// import { getDataFromTree } from 'react-apollo';\n\nconst getApplicationStream = (originalUrl, context) => {\n  const helmetContext = {}\n  const app = (\n    <HelmetProvider context={helmetContext}>\n      <StaticRouter location={originalUrl} context={context}>\n        <App />\n      </StaticRouter>\n    </HelmetProvider>\n  )\n\n  const sheet = new ServerStyleSheet()\n  return sheet.interleaveWithNodeStream(\n    renderToNodeStream(sheet.collectStyles(app))\n  )\n}\n\nexport function write (data) {\n  this.queue(data)\n}\n\nexport const end = endingHTMLFragment =>\n  function end () {\n    this.queue(endingHTMLFragment)\n    this.queue(null)\n  }\n\nexport const ssr = getApplicationStream => (req, res) => {\n  try {\n    // If you were using Apollo, you could fetch data with this\n    // await getDataFromTree(app);\n\n    const context = {}\n    const stream = getApplicationStream(req.originalUrl, context)\n\n    if (context.url) {\n      return res.redirect(301, context.url)\n    }\n\n    const [startingHTMLFragment, endingHTMLFragment] = getHTMLFragments({\n      drainHydrateMarks: printDrainHydrateMarks()\n    })\n\n    res.status(200)\n    res.write(startingHTMLFragment)\n    stream.pipe(through(write, end(endingHTMLFragment))).pipe(res)\n  } catch (e) {\n    log.error(e)\n    res.status(500)\n    res.end()\n  }\n}\n\nconst defaultSSR = ssr(getApplicationStream)\n\nexport default defaultSSR\n"
  }
]