Rewrite frontend using create-react-app

This commit is contained in:
MICHAEL JACKSON
2017-03-25 23:53:54 -07:00
parent 69a578fcda
commit 0f5c584104
60 changed files with 20489 additions and 18930 deletions

View File

@ -1,10 +0,0 @@
{
"presets": [
[ "es2015", { "loose": true } ],
"stage-1",
"react"
],
"plugins": [
"transform-object-assign"
]
}

View File

@ -1,4 +0,0 @@
module.exports = [
'goodjsproject',
'thisoneisevil'
]

View File

@ -1,5 +0,0 @@
{
"env": {
"browser": true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -1,13 +0,0 @@
import {
continents as ContinentsIndex,
countries as CountriesIndex
} from 'countries-list'
const getCountriesByContinent = (continent) =>
Object.keys(CountriesIndex).filter(country => CountriesIndex[country].continent === continent)
export {
ContinentsIndex,
CountriesIndex,
getCountriesByContinent
}

View File

@ -1,15 +0,0 @@
export const addEvent = (node, type, handler) => {
if (node.addEventListener) {
node.addEventListener(type, handler, false)
} else if (node.attachEvent) {
node.attachEvent('on' + type, handler)
}
}
export const removeEvent = (node, type, handler) => {
if (node.removeEventListener) {
node.removeEventListener(type, handler, false)
} else if (node.detachEvent) {
node.detachEvent('on' + type, handler)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,15 +0,0 @@
export const formatNumber = (n) => {
const digits = String(n).split('')
const groups = []
while (digits.length)
groups.unshift(digits.splice(-3).join(''))
return groups.join(',')
}
export const parseNumber = (s) =>
parseInt(s.replace(/,/g, ''), 10) || 0
export const formatPercent = (n, fixed = 1) =>
String((n.toPrecision(2) * 100).toFixed(fixed))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,9 +0,0 @@
import React from 'react'
import contentHTML from './About.md'
class About extends React.Component {
render = () =>
<div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }}/>
}
export default About

View File

@ -1,50 +0,0 @@
unpkg is an [open source](https://github.com/mjackson/unpkg) project built by me, [Michael Jackson](https://twitter.com/mjackson). I built it because, as an npm package author, it felt tedious for me to use existing, git-based CDNs to make my open source work available via CDN. Development was sponsored by my company, [React Training](https://reacttraining.com).
<div class="about-logos">
<div class="about-logo">
<a href="https://reacttraining.com"><img src="../ReactTrainingLogo.png"></a>
</div>
</div>
We'd love to talk to you more about training your team on [React](https://facebook.github.io/react/). Please [get in touch](mailto:hello@reacttraining.com) if interested.
### Sponsors
The fast, global infrastructure that powers unpkg is generously donated by [Cloudflare](https://www.cloudflare.com) and [Heroku](https://www.heroku.com).
<div class="about-logos">
<div class="about-logo">
<a href="https://www.cloudflare.com"><img src="../CloudflareLogo.png"></a>
</div>
<div class="about-logo">
<a href="https://www.heroku.com"><img src="../HerokuLogo.png"></a>
</div>
</div>
These sponsors provide some of the most robust, reliable infrastructure available today and I'm happy to be able to partner with them on unpkg.
### Cache Behavior
The CDN caches all files based on their permanent URL, which includes the npm package version. This works because npm does not allow package authors to overwrite a package that has already been published with a different one at the same version number.
URLs that do not specify a package version number redirect to one that does. This is the `latest` version when no version is specified, or the `maxSatisfying` version when a [semver version](https://github.com/npm/node-semver) is given. Redirects are cached for 5 minutes.
Browsers are instructed (via the `Cache-Control` header) to cache assets for 4 hours.
### Support
unpkg is a free, best-effort service and cannot provide any uptime or support guarantees.
I do my best to keep it running, but sometimes things go wrong. Sometimes there are network or provider issues outside my control. Sometimes abusive traffic temporarily affects response times. Sometimes I break things by doing something dumb, but I try not to.
The goal of unpkg is to provide a hassle-free CDN for npm package authors. It's also a great resource for people creating demos and instructional material. However, if you rely on it to serve files that are crucial to your business, you should probably pay for a host with well-supported infrastructure and uptime guarantees.
unpkg is not affiliated with or supported by npm, Inc. in any way. Please do not contact npm for help with unpkg.
### Abuse
unpkg blacklists some packages to prevent abuse. If you find a malicious package on npm, please take a moment to add it to [our blacklist](https://github.com/mjackson/unpkg/blob/master/modules/PackageBlacklist.js)!
### Feedback
If you think this is useful, I'd love to hear from you. Please reach out to [@mjackson](https://twitter.com/mjackson) with any questions/concerns.

View File

@ -1,12 +0,0 @@
import React from 'react'
import contentHTML from './Home.md'
class Home extends React.Component {
render() {
return (
<div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }}/>
)
}
}
export default Home

View File

@ -1,62 +0,0 @@
unpkg is a fast, global [content delivery network](https://en.wikipedia.org/wiki/Content_delivery_network) for stuff that is published to [npm](https://www.npmjs.com/). Use it to quickly and easily load files using a simple URL like:
<div style="text-align:center">`https://unpkg.com/package@version/file`</div>
A few examples:
* [https://unpkg.com/react@15.3.1/dist/react.min.js](/react@15.3.1/dist/react.min.js)
* [https://unpkg.com/react-dom@15.3.1/dist/react-dom.min.js](/react-dom@15.3.1/dist/react-dom.min.js)
* [https://unpkg.com/history@4.2.0/umd/history.min.js](/history@4.2.0/umd/history.min.js)
You may also use a [tag](https://docs.npmjs.com/cli/dist-tag) or [version range](https://docs.npmjs.com/misc/semver) instead of a fixed version number, or omit the version/tag entirely to use the `latest` tag.
* [https://unpkg.com/react@^0.14/dist/react.min.js](/react@^0.14/dist/react.min.js)
* [https://unpkg.com/react/dist/react.min.js](/react/dist/react.min.js)
If you omit the file path, unpkg will try to serve [the `browser` bundle](https://github.com/defunctzombie/package-browser-field-spec) if present, the [`main` module](https://docs.npmjs.com/files/package.json#main) otherwise.
* [https://unpkg.com/jquery](/jquery)
* [https://unpkg.com/angular-formly](/angular-formly)
* [https://unpkg.com/three](/three)
Append a `/` at the end of a URL to view a listing of all the files in a package.
* [https://unpkg.com/lodash/](/lodash/)
* [https://unpkg.com/modernizr/](/modernizr/)
* [https://unpkg.com/react/](/react/)
### Query Parameters
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th width="80px">Name</th>
<th width="120px">Default Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>`main`</td>
<td>`unpkg`, `browser`, `main`</td>
<td>The name of the field in [package.json](https://docs.npmjs.com/files/package.json) to use as the main entry point when there is no file path in the URL.</td>
</tr>
<tr>
<td>`json`</td>
<td>`undefined`</td>
<td>Return a recursive list of metadata about all the files in a directory as JSON (e.g. `/any/path/?json`). Note: this only works for directories.</td>
</tr>
</tbody>
</table>
### Suggested Workflow
For npm package authors, unpkg relieves the burden of publishing your code to a CDN in addition to the npm registry. All you need to do is include your [UMD](https://github.com/umdjs/umd) build in your npm package (not your repo, that's different!).
You can do this easily using the following setup:
* Add the `umd` (or `dist`) directory to your `.gitignore` file
* Add the `umd` directory to your [files array](https://docs.npmjs.com/files/package.json#files) in `package.json`
* Use a build script to generate your UMD build in the `umd` directory when you publish
That's it! Now when you `npm publish` you'll have a version available on unpkg as well.

View File

@ -1,89 +0,0 @@
import React, { PropTypes } from 'react'
import { Motion, spring } from 'react-motion'
import { findDOMNode } from 'react-dom'
import Window from './Window'
class Layout extends React.Component {
static propTypes = {
location: PropTypes.object,
children: PropTypes.node
}
state = {
underlineLeft: 0,
underlineWidth: 0,
useSpring: false
}
adjustUnderline = (useSpring = false) => {
let itemIndex
switch (this.props.location.pathname) {
case '/about':
itemIndex = 2
break
case '/stats':
itemIndex = 1
break
case '/':
default:
itemIndex = 0
}
const itemNodes = findDOMNode(this).querySelectorAll('.underlist > li')
const currentNode = itemNodes[itemIndex]
this.setState({
underlineLeft: currentNode.offsetLeft,
underlineWidth: currentNode.offsetWidth,
useSpring
})
}
componentDidMount = () =>
this.adjustUnderline()
componentDidUpdate = (prevProps) => {
if (prevProps.location.pathname !== this.props.location.pathname)
this.adjustUnderline(true)
}
render = () => {
const { underlineLeft, underlineWidth, useSpring } = this.state
const style = {
left: useSpring ? spring(underlineLeft, { stiffness: 220 }) : underlineLeft,
width: useSpring ? spring(underlineWidth) : underlineWidth
}
return (
<div>
<Window onResize={this.adjustUnderline}/>
<header>
<h1>unpkg</h1>
<nav>
<ol className="underlist">
<li><a href="#/">Home</a></li>
<li><a href="#/stats">Stats</a></li>
<li><a href="#/about">About</a></li>
</ol>
<Motion defaultStyle={{ left: underlineLeft, width: underlineWidth }} style={style}>
{s => (
<div
className="underlist-underline"
style={{
WebkitTransform: `translate3d(${s.left}px,0,0)`,
transform: `translate3d(${s.left}px,0,0)`,
width: s.width
}}
/>
)}
</Motion>
</nav>
</header>
{this.props.children}
</div>
)
}
}
export default Layout

View File

@ -1,41 +0,0 @@
import React, { PropTypes } from 'react'
import { parseNumber, formatNumber } from '../NumberUtils'
class NumberTextInput extends React.Component {
static propTypes = {
value: PropTypes.number,
parseNumber: PropTypes.func,
formatNumber: PropTypes.func,
onChange: PropTypes.func
}
static defaultProps = {
value: 0,
parseNumber,
formatNumber
}
componentWillMount = () =>
this.setState({ value: this.props.value })
handleChange = (event) => {
const value = this.props.parseNumber(event.target.value)
this.setState({ value }, () => {
if (this.props.onChange)
this.props.onChange(value)
})
}
render = () => {
const { value } = this.state
const { parseNumber, formatNumber, ...props } = this.props // eslint-disable-line no-unused-vars
const displayValue = formatNumber(value)
return (
<input {...props} type="text" value={displayValue} onChange={this.handleChange}/>
)
}
}
export default NumberTextInput

View File

@ -1,51 +0,0 @@
import React from 'react'
import history from '../history'
import Layout from './Layout'
import About from './About'
import Stats from './Stats'
import Home from './Home'
const findMatchingComponents = (location) => {
let components
switch (location.pathname) {
case '/about':
components = [ Layout, About ]
break
case '/stats':
components = [ Layout, Stats ]
break
case '/':
default:
components = [ Layout, Home ]
}
return components
}
const renderNestedComponents = (components, props) =>
components.reduceRight(
(children, component) => React.createElement(component, { ...props, children }),
undefined
)
class Router extends React.Component {
state = {
location: history.getCurrentLocation()
}
componentDidMount = () =>
this.unlisten = history.listen(location => {
this.setState({ location })
})
componentWillUnmount = () =>
this.unlisten()
render = () => {
const { location } = this.state
const components = findMatchingComponents(location)
return renderNestedComponents(components, { location })
}
}
export default Router

View File

@ -1,145 +0,0 @@
import React, { PropTypes } from 'react'
import formatBytes from 'byte-size'
import formatDate from 'date-fns/format'
import parseDate from 'date-fns/parse'
import { formatNumber, formatPercent } from '../NumberUtils'
import { ContinentsIndex, CountriesIndex, getCountriesByContinent } from '../CountryUtils'
import NumberTextInput from './NumberTextInput'
const getSum = (data, countries) =>
countries.reduce((n, country) => n + (data[country] || 0), 0)
const addValues = (a, b) => {
for (const p in b) {
if (p in a) {
a[p] += b[p]
} else {
a[p] = b[p]
}
}
}
class Stats extends React.Component {
static propTypes = {
stats: PropTypes.object
}
static defaultProps = {
stats: window.cloudFlareStats
}
state = {
minRequests: 5000000
}
updateMinRequests = (value) =>
this.setState({ minRequests: value })
render = () => {
const { minRequests } = this.state
const { stats } = this.props
const { timeseries, totals } = stats
// Summary data
const sinceDate = parseDate(totals.since)
const untilDate = parseDate(totals.until)
const uniqueVisitors = totals.uniques.all
const totalRequests = totals.requests.all
const cachedRequests = totals.requests.cached
const totalBandwidth = totals.bandwidth.all
const httpStatus = totals.requests.http_status
let errorRequests = 0
for (const status in httpStatus) {
if (httpStatus.hasOwnProperty(status) && status >= 500)
errorRequests += httpStatus[status]
}
// By Region
const regionRows = []
const requestsByCountry = {}
const bandwidthByCountry = {}
timeseries.forEach(ts => {
addValues(requestsByCountry, ts.requests.country)
addValues(bandwidthByCountry, ts.bandwidth.country)
})
const byRequestsDescending = (a, b) =>
requestsByCountry[b] - requestsByCountry[a]
const continentData = Object.keys(ContinentsIndex).reduce((memo, continent) => {
const countries = getCountriesByContinent(continent)
memo[continent] = {
countries,
requests: getSum(requestsByCountry, countries),
bandwidth: getSum(bandwidthByCountry, countries)
}
return memo
}, {})
const topContinents = Object.keys(continentData).sort((a, b) => {
return continentData[b].requests - continentData[a].requests
})
topContinents.forEach(continent => {
const continentName = ContinentsIndex[continent]
const { countries, requests, bandwidth } = continentData[continent]
if (bandwidth !== 0) {
regionRows.push(
<tr key={continent} className="continent-row">
<td>{continentName}</td>
<td>{formatNumber(requests)} ({formatPercent(requests / totalRequests)}%)</td>
<td>{formatBytes(bandwidth)} ({formatPercent(bandwidth / totalBandwidth)}%)</td>
</tr>
)
const topCountries = countries.sort(byRequestsDescending)
topCountries.forEach(country => {
const countryRequests = requestsByCountry[country]
const countryBandwidth = bandwidthByCountry[country]
if (countryRequests > minRequests) {
regionRows.push(
<tr key={continent + country} className="country-row">
<td className="country-name">{CountriesIndex[country].name}</td>
<td>{formatNumber(countryRequests)} ({formatPercent(countryRequests / totalRequests)}%)</td>
<td>{formatBytes(countryBandwidth)} ({formatPercent(countryBandwidth / totalBandwidth)}%)</td>
</tr>
)
}
})
}
})
return (
<div className="wrapper">
<p>From <strong>{formatDate(sinceDate, 'MMM D')}</strong> to <strong>{formatDate(untilDate, 'MMM D')}</strong>, unpkg served <strong>{formatNumber(totalRequests)}</strong> requests to <strong>{formatNumber(uniqueVisitors)}</strong> unique visitors, <strong>{formatPercent(cachedRequests / totalRequests, 0)}%</strong> of which came from the cache (CDN). Over the same period, <strong>{formatPercent(errorRequests / totalRequests, 4)}%</strong> of requests resulted in server error (returned an HTTP status &ge; 500).</p>
<h3>By Region</h3>
<label className="table-filter">Include countries that made at least <NumberTextInput value={minRequests} onChange={this.updateMinRequests}/> requests.</label>
<table cellSpacing="0" cellPadding="0" style={{ width: '100%' }}>
<thead>
<tr>
<th>Name</th>
<th>Requests (% of total)</th>
<th>Bandwidth (% of total)</th>
</tr>
</thead>
<tbody>
{regionRows}
</tbody>
</table>
</div>
)
}
}
export default Stats

View File

@ -1,26 +0,0 @@
import React, { PropTypes } from 'react'
import { addEvent, removeEvent } from '../DOMUtils'
const ResizeEvent = 'resize'
class Window extends React.Component {
static propTypes = {
onResize: PropTypes.func
}
handleWindowResize = () => {
if (this.props.onResize)
this.props.onResize()
}
componentDidMount = () =>
addEvent(window, ResizeEvent, this.handleWindowResize)
componentWillUnmount = () =>
removeEvent(window, ResizeEvent, this.handleWindowResize)
render = () =>
null
}
export default Window

View File

@ -1,5 +0,0 @@
import createHistory from 'history/lib/createHashHistory'
const history = createHistory()
export default history

View File

@ -1,10 +0,0 @@
import React from 'react'
import { render } from 'react-dom'
import Router from './components/Router'
import './styles.css'
render(
<Router/>,
document.getElementById('app')
)

View File

@ -1,127 +0,0 @@
body {
font-size: 16px;
font-family: -apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Helvetica,
Arial,
sans-serif;
line-height: 1.5;
padding: 5px 20px;
}
@media all and (min-width: 660px) {
body {
padding: 50px 20px;
}
}
a:link {
color: blue;
}
a:visited {
color: rebeccapurple;
}
code {
background: #eee;
}
table {
border-color: black;
border-style: solid;
border-width: 0 0 1px 1px;
}
table th, table td {
text-align: left;
vertical-align: top;
padding: 5px 7px;
border-color: black;
border-style: solid;
border-width: 1px 1px 0 0;
}
.continent-row {
font-weight: bold;
}
.country-name {
padding-left: 2em;
}
.table-filter {
display: block;
text-align: right;
color: #666;
margin-bottom: 1em;
}
.table-filter input {
font-size: 1em;
text-align: right;
max-width: 100px;
color: #999;
}
header, .wrapper {
max-width: 600px;
margin: 0 auto;
}
h1, h2, h3, header nav {
font-family: Futura, Helvetica, sans-serif;
text-transform: uppercase;
}
h3 {
margin-top: 2em;
}
header h1 {
font-size: 4em;
line-height: 1;
text-align: center;
letter-spacing: 0.1em;
}
header nav {
margin-bottom: 4em;
}
header nav a:link,
header nav a:visited {
color: black;
}
.underlist {
list-style-type: none;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
}
.underlist li {
margin: 0 5px;
padding: 0 5px;
flex-basis: auto;
}
.underlist a:link {
text-decoration: none;
}
.underlist-underline {
height: 4px;
background-color: black;
position: absolute;
left: 0;
}
.about-logos {
margin: 2em 0;
display: flex;
justify-content: center;
}
.about-logo {
text-align: center;
flex: 1;
max-width: 80%;
}
.about-logo img {
max-width: 60%;
}

View File

@ -1,3 +0,0 @@
require('babel-register')({
only: __dirname
})

View File

@ -1,147 +0,0 @@
const fs = require('fs')
const invariant = require('invariant')
const webpack = require('webpack')
const createBundle = (webpackStats) => {
const { publicPath, assetsByChunkName } = webpackStats
const createURL = (asset) =>
publicPath + asset
const getAssets = (chunks = [ 'main' ]) =>
(Array.isArray(chunks) ? chunks : [ chunks ]).reduce((memo, chunk) => (
memo.concat(assetsByChunkName[chunk] || [])
), [])
const getScriptAssets = (...args) =>
getAssets(...args)
.filter(asset => (/\.js$/).test(asset))
.map(createURL)
const getStyleAssets = (...args) =>
getAssets(...args)
.filter(asset => (/\.css$/).test(asset))
.map(createURL)
return {
createURL,
getAssets,
getScriptAssets,
getStyleAssets
}
}
/**
* An express middleware that sets req.manifest from the build manifest
* in the given file. Should be used in production to get consistent hashes.
*/
const assetsManifest = (webpackManifestFile) => {
let manifest
try {
manifest = JSON.parse(fs.readFileSync(webpackManifestFile, 'utf8'))
} catch (error) {
invariant(
false,
'assetsManifest middleware cannot read the manifest file "%s"; ' +
'do `npm run build` before starting the server',
webpackManifestFile
)
}
return (req, res, next) => {
req.manifest = manifest
next()
}
}
/**
* An express middleware that sets req.bundle from the build
* info in the given stats file. Should be used in production.
*/
const staticAssets = (webpackStatsFile) => {
let stats
try {
stats = JSON.parse(fs.readFileSync(webpackStatsFile, 'utf8'))
} catch (error) {
invariant(
false,
'staticAssets middleware cannot read the build stats in %s; ' +
'do `npm run build` before starting the server',
webpackStatsFile
)
}
const bundle = createBundle(stats)
return (req, res, next) => {
req.bundle = bundle
next()
}
}
/**
* An express middleware that sets req.bundle from the
* latest result from a running webpack compiler (i.e. using
* webpack-dev-middleware). Should only be used in dev.
*/
const devAssets = (webpackCompiler) => {
let bundle
webpackCompiler.plugin('done', (stats) => {
bundle = createBundle(stats.toJson())
})
return (req, res, next) => {
invariant(
bundle != null,
'devAssets middleware needs a running compiler; ' +
'use webpack-dev-middleware in front of devAssets'
)
req.bundle = bundle
next()
}
}
/**
* Creates a webpack compiler that automatically inlines the
* webpack dev runtime in all entry points.
*/
const createDevCompiler = (webpackConfig, webpackRuntimeModuleID) =>
webpack({
...webpackConfig,
entry: prependModuleID(
webpackConfig.entry,
webpackRuntimeModuleID
)
})
/**
* Returns a modified copy of the given webpackEntry object with
* the moduleID in front of all other assets.
*/
const prependModuleID = (webpackEntry, moduleID) => {
if (typeof webpackEntry === 'string')
return [ moduleID, webpackEntry ]
if (Array.isArray(webpackEntry))
return [ moduleID, ...webpackEntry ]
if (webpackEntry && typeof webpackEntry === 'object') {
const entry = { ...webpackEntry }
for (const chunkName in entry)
if (entry.hasOwnProperty(chunkName))
entry[chunkName] = prependModuleID(entry[chunkName], moduleID)
return entry
}
throw new Error('Invalid webpack entry object')
}
module.exports = {
assetsManifest,
staticAssets,
devAssets,
createDevCompiler
}

View File

@ -1,81 +0,0 @@
require('isomorphic-fetch')
const { createStack, createFetch, header, base, query, parseJSON, onResponse } = require('http-client')
const invariant = require('invariant')
const CloudflareKey = process.env.CLOUDFLARE_KEY
const CloudflareEmail = process.env.CLOUDFLARE_EMAIL
invariant(
CloudflareKey,
'Missing $CLOUDFLARE_KEY environment variable'
)
invariant(
CloudflareEmail,
'Missing $CLOUDFLARE_EMAIL environment variable'
)
const createRangeQuery = (since, until) =>
query({
since: since.toISOString(),
until: until.toISOString()
})
const createNameQuery = (name) =>
query({ name })
const getResult = () =>
createStack(
parseJSON(),
onResponse(response => response.jsonData.result)
)
const commonStack = createStack(
header('X-Auth-Key', CloudflareKey),
header('X-Auth-Email', CloudflareEmail),
base('https://api.cloudflare.com/client/v4'),
getResult()
)
const getZones = (domainName) =>
createFetch(
commonStack,
createNameQuery(domainName)
)('/zones')
const getZoneAnalyticsDashboard = (zone, since, until) =>
createFetch(
commonStack,
createRangeQuery(since, until)
)(`/zones/${zone.id}/analytics/dashboard`)
const getAnalyticsDashboards = (domainNames, since, until) =>
Promise.all(
domainNames.map(domainName => getZones(domainName))
).then(
domainZones => domainZones.reduce((memo, zones) => memo.concat(zones))
).then(
zones => Promise.all(zones.map(zone => getZoneAnalyticsDashboard(zone, since, until)))
).then(
results => results.reduce(reduceResults)
)
const reduceResults = (target, results) => {
Object.keys(results).forEach(key => {
const value = results[key]
if (typeof value === 'object' && value) {
target[key] = reduceResults(target[key] || {}, value)
} else if (typeof value === 'number') {
target[key] = (target[key] || 0) + results[key]
}
})
return target
}
module.exports = {
getZones,
getZoneAnalyticsDashboard,
getAnalyticsDashboards
}

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
const React = require('react')
const { renderToStaticMarkup } = require('react-dom/server')
const { getAnalyticsDashboards } = require('./Cloudflare')
const HomePage = require('./components/HomePage')
const OneMinute = 1000 * 60
const ThirtyDays = OneMinute * 60 * 24 * 30
const DOCTYPE = '<!DOCTYPE html>'
const fetchStats = (callback) => {
if (process.env.NODE_ENV === 'development') {
callback(null, require('./CloudflareStats.json'))
} else {
const since = new Date(Date.now() - ThirtyDays)
const until = new Date(Date.now() - OneMinute)
getAnalyticsDashboards([ 'npmcdn.com', 'unpkg.com' ], since, until)
.then(result => callback(null, result), callback)
}
}
const sendHomePage = (req, res, next) => {
const chunks = [ 'vendor', 'home' ]
const props = {
styles: req.bundle.getStyleAssets(chunks),
scripts: req.bundle.getScriptAssets(chunks)
}
if (req.manifest)
props.webpackManifest = req.manifest
fetchStats((error, stats) => {
if (error) {
next(error)
} else {
res.set('Cache-Control', 'public, max-age=60')
res.send(
DOCTYPE + renderToStaticMarkup(<HomePage {...props} stats={stats}/>)
)
}
})
}
module.exports = {
sendHomePage
}

View File

@ -1,18 +0,0 @@
const path = require('path')
exports.id = 1
exports.port = parseInt(process.env.PORT, 10) || 5000
exports.webpackConfig = require('../../webpack.config')
exports.statsFile = path.resolve(__dirname, '../../stats.json')
exports.publicDir = path.resolve(__dirname, '../../public')
exports.manifestFile = path.resolve(exports.publicDir, '__assets__/chunk-manifest.json')
exports.timeout = parseInt(process.env.TIMEOUT, 10) || 20000
exports.maxAge = process.env.MAX_AGE || '365d'
exports.registryURL = process.env.REGISTRY_URL || 'https://registry.npmjs.org'
exports.bowerBundle = process.env.BOWER_BUNDLE || '/bower.zip'
exports.redirectTTL = process.env.REDIRECT_TTL || 500
exports.autoIndex = !process.env.DISABLE_INDEX
exports.redisURL = process.env.REDIS_URL
exports.blacklist = require('../PackageBlacklist')

View File

@ -1,31 +0,0 @@
const redis = require('redis')
const onFinished = require('on-finished')
const URLFormat = /^\/((?:@[^\/@]+\/)?[^\/@]+)(?:@([^\/]+))?(\/.*)?$/
const logStats = (redisURL) => {
const redisClient = redis.createClient(redisURL)
return (req, res, next) => {
onFinished(res, () => {
const path = req.path
if (res.statusCode === 200 && path.charAt(path.length - 1) !== '/') {
//redisClient.zincrby([ 'request-paths', 1, path ])
const match = URLFormat.exec(path)
if (match) {
const packageName = match[1]
redisClient.zincrby([ 'package-requests', 1, packageName ])
}
}
})
next()
}
}
module.exports = {
logStats
}

View File

@ -1,48 +0,0 @@
const React = require('react')
const PropTypes = React.PropTypes
class HomePage extends React.Component {
static propTypes = {
webpackManifest: PropTypes.object,
styles: PropTypes.arrayOf(PropTypes.string),
scripts: PropTypes.arrayOf(PropTypes.string),
stats: PropTypes.object
}
static defaultProps = {
webpackManifest: {},
styles: [],
scripts: [],
stats: {}
}
render() {
const { webpackManifest, styles, scripts, stats } = this.props
return (
<html>
<head>
<meta charSet="utf-8"/>
<meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="description" content="A fast, global content delivery network for stuff that is published to npm"/>
<meta name="viewport" content="width=700,maximum-scale=1"/>
<meta name="timestamp" content={(new Date).toISOString()}/>
<link rel="icon" href="/favicon.ico?v3"/>
<title>unpkg</title>
<script dangerouslySetInnerHTML={{ __html: "window.Promise || document.write('\\x3Cscript src=\"/es6-promise.min.js\">\\x3C/script>\\x3Cscript>ES6Promise.polyfill()\\x3C/script>')" }}/>
<script dangerouslySetInnerHTML={{ __html: "window.fetch || document.write('\\x3Cscript src=\"/fetch.min.js\">\\x3C/script>')" }}/>
<script dangerouslySetInnerHTML={{ __html: "window.webpackManifest = " + JSON.stringify(webpackManifest) }}/>
<script dangerouslySetInnerHTML={{ __html: "window.cloudFlareStats = " + JSON.stringify(stats) }}/>
{styles.map(s => <link rel="stylesheet" key={s} href={s}/>)}
</head>
<body>
<div id="app"/>
{scripts.map(s => <script key={s} src={s}/>)}
</body>
</html>
)
}
}
module.exports = HomePage

View File

@ -1,136 +0,0 @@
/*eslint-disable no-console*/
const http = require('http')
const cors = require('cors')
const throng = require('throng')
const morgan = require('morgan')
const express = require('express')
const devErrorHandler = require('errorhandler')
const WebpackDevServer = require('webpack-dev-server')
const { createRequestHandler } = require('express-unpkg')
const DefaultServerConfig = require('./ServerConfig')
const { assetsManifest, staticAssets, devAssets, createDevCompiler } = require('./AssetsUtils')
const { sendHomePage } = require('./MainController')
const { logStats } = require('./StatsUtils')
const createRouter = (config = {}) => {
const router = express.Router()
router.get('/', sendHomePage)
if (config.redisURL)
router.use(logStats(config.redisURL))
router.use(createRequestHandler(config))
return router
}
const errorHandler = (err, req, res, next) => {
res.status(500).send('<p>Internal Server Error</p>')
console.error(err.stack)
next(err)
}
const createServer = (config) => {
const app = express()
app.disable('x-powered-by')
app.use(errorHandler)
app.use(cors())
app.use(express.static(config.publicDir, { maxAge: config.maxAge }))
app.use(assetsManifest(config.manifestFile))
app.use(staticAssets(config.statsFile))
app.use(createRouter(config))
const server = http.createServer(app)
// Heroku dynos automatically timeout after 30s. Set our
// own timeout here to force sockets to close before that.
// https://devcenter.heroku.com/articles/request-timeout
if (config.timeout) {
server.setTimeout(config.timeout, (socket) => {
const message = `Timeout of ${config.timeout}ms exceeded`
socket.end([
`HTTP/1.1 503 Service Unavailable`,
`Date: ${(new Date).toGMTString()}`,
`Content-Type: text/plain`,
`Content-Length: ${message.length}`,
`Connection: close`,
``,
message
].join(`\r\n`))
})
}
return server
}
const createDevServer = (config) => {
const webpackConfig = config.webpackConfig
const compiler = createDevCompiler(
webpackConfig,
`webpack-dev-server/client?http://localhost:${config.port}`
)
const server = new WebpackDevServer(compiler, {
// webpack-dev-middleware options.
publicPath: webpackConfig.output.publicPath,
quiet: false,
noInfo: false,
stats: {
// https://webpack.github.io/docs/node.js-api.html#stats-tojson
assets: true,
colors: true,
version: true,
hash: true,
timings: true,
chunks: false
},
// webpack-dev-server options.
contentBase: false,
setup(app) {
// This runs before webpack-dev-middleware.
app.disable('x-powered-by')
app.use(morgan('dev'))
}
})
// This runs after webpack-dev-middleware.
server.use(devErrorHandler())
server.use(express.static(config.publicDir))
server.use(devAssets(compiler))
server.use(createRouter(config))
return server
}
const startServer = (serverConfig) => {
const config = {
...DefaultServerConfig,
...serverConfig
}
const server = process.env.NODE_ENV === 'production'
? createServer(config)
: createDevServer(config)
server.listen(config.port, () => {
console.log('Server #%s listening on port %s, Ctrl+C to stop', config.id, config.port)
})
}
module.exports = {
createServer,
createDevServer,
startServer
}
if (require.main === module)
throng({
start: (id) => startServer({ id }),
workers: process.env.WEB_CONCURRENCY || 1,
lifetime: Infinity
})