Streamline home page
This commit is contained in:
parent
59be66f552
commit
d2637cb9ea
140
docs/api.md
140
docs/api.md
|
@ -1,140 +0,0 @@
|
||||||
# Authentication
|
|
||||||
|
|
||||||
Some API methods require an authentication token. This token is a [JSON web token](https://en.wikipedia.org/wiki/JSON_Web_Token) that contains a list of "scopes" (i.e. permissions).
|
|
||||||
|
|
||||||
Once you obtain an API token ([see below](#post-api-auth)) you simply include it in the `Authorization` header of your request as a base-64 encoded string, i.e.
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: base64(token)
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /api/publicKey
|
|
||||||
|
|
||||||
The [public key](https://en.wikipedia.org/wiki/Public-key_cryptography) unpkg uses to encrypt authentication tokens, as JSON. You can also find the key as plain text [on GitHub](https://github.com/unpkg/unpkg.com/blob/master/secret_key.pub).
|
|
||||||
|
|
||||||
This can be useful to verify a token was issued by unpkg.
|
|
||||||
|
|
||||||
Required scope: none
|
|
||||||
|
|
||||||
Query parameters: none
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl "https://unpkg.com/api/publicKey"
|
|
||||||
{
|
|
||||||
"publicKey": "..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /api/auth
|
|
||||||
|
|
||||||
Creates and returns a new auth token. By default, auth tokens have the following scopes:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"blacklist": {
|
|
||||||
"read": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Required scope: none
|
|
||||||
|
|
||||||
Body parameters: none
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl -X POST "https://unpkg.com/api/auth"
|
|
||||||
{
|
|
||||||
"token": "..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Please reach out to @mjackson if you need a token with additional scopes.
|
|
||||||
|
|
||||||
### GET /api/auth
|
|
||||||
|
|
||||||
Verifies and returns the payload contained in the given auth token.
|
|
||||||
|
|
||||||
Required scope: none
|
|
||||||
|
|
||||||
Query parameters: none
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl -H "Authorization: $BASE_64_ENCODED_TOKEN" "https://unpkg.com/api/auth"
|
|
||||||
{
|
|
||||||
"jti": "...",
|
|
||||||
"iss": "https://unpkg.com",
|
|
||||||
"iat": ...,
|
|
||||||
"scopes": { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Blacklist
|
|
||||||
|
|
||||||
To protect unpkg users and prevent abuse, unpkg manages a blacklist of npm packages that are known to contain harmful code.
|
|
||||||
|
|
||||||
### GET /api/blacklist
|
|
||||||
|
|
||||||
Returns a list of all packages that are currently blacklisted.
|
|
||||||
|
|
||||||
Required scope: `blacklist.read`
|
|
||||||
|
|
||||||
Query parameters: none
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl -H "Authorization: $BASE_64_ENCODED_TOKEN" "https://unpkg.com/api/blacklist"
|
|
||||||
{
|
|
||||||
"blacklist": [ ... ]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /api/blacklist
|
|
||||||
|
|
||||||
Adds a package to the blacklist.
|
|
||||||
|
|
||||||
Required scope: `blacklist.add`
|
|
||||||
|
|
||||||
Body parameters:
|
|
||||||
|
|
||||||
* `packageName` - The package to add to the blacklist (required)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl -H "Authorization: $BASE_64_ENCODED_TOKEN" -d '{"packageName":"bad-package"}' "https://unpkg.com/api/blacklist"
|
|
||||||
{
|
|
||||||
"ok": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### DELETE /api/blacklist
|
|
||||||
|
|
||||||
Removes a package from the blacklist.
|
|
||||||
|
|
||||||
Required scope: `blacklist.remove`
|
|
||||||
|
|
||||||
Body parameters:
|
|
||||||
|
|
||||||
* `packageName` - The package to remove from the blacklist (required)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```log
|
|
||||||
> curl -X DELETE -H "Authorization: $BASE_64_ENCODED_TOKEN" -d '{"packageName":"bad-package"}' "https://unpkg.com/api/blacklist"
|
|
||||||
{
|
|
||||||
"ok": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Stats
|
|
||||||
|
|
||||||
### GET /api/stats
|
|
||||||
|
|
||||||
TODO
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
48
docs/home.md
48
docs/home.md
|
@ -1,48 +0,0 @@
|
||||||
unpkg is a fast, global [content delivery network](https://en.wikipedia.org/wiki/Content_delivery_network) for everything on [npm](https://www.npmjs.com/). Use it to quickly and easily load any file from any package using a URL like:
|
|
||||||
|
|
||||||
<div class="home-example">unpkg.com/:package@:version/:file</div>
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
Using a fixed version:
|
|
||||||
|
|
||||||
* [unpkg.com/react@16.0.0/umd/react.production.min.js](/react@16.0.0/umd/react.production.min.js)
|
|
||||||
* [unpkg.com/react-dom@16.0.0/umd/react-dom.production.min.js](/react-dom@16.0.0/umd/react-dom.production.min.js)
|
|
||||||
|
|
||||||
You may also use a [semver range](https://docs.npmjs.com/misc/semver) or a [tag](https://docs.npmjs.com/cli/dist-tag) instead of a fixed version number, or omit the version/tag entirely to use the `latest` tag.
|
|
||||||
|
|
||||||
* [unpkg.com/react@^16/umd/react.production.min.js](/react@^16/umd/react.production.min.js)
|
|
||||||
* [unpkg.com/react/umd/react.production.min.js](/react/umd/react.production.min.js)
|
|
||||||
|
|
||||||
If you omit the file path (i.e. use a "bare" URL), unpkg will serve the file specified by the `unpkg` field in `package.json`, or fall back to `main`.
|
|
||||||
|
|
||||||
* [unpkg.com/d3](/d3)
|
|
||||||
* [unpkg.com/jquery](/jquery)
|
|
||||||
* [unpkg.com/three](/three)
|
|
||||||
|
|
||||||
Append a `/` at the end of a URL to view a listing of all the files in a package.
|
|
||||||
|
|
||||||
* [unpkg.com/react/](/react/)
|
|
||||||
* [unpkg.com/lodash/](/lodash/)
|
|
||||||
|
|
||||||
### Query Parameters
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>`?meta`</dt>
|
|
||||||
<dd>Return metadata about any file in a package as JSON (e.g. `/any/file?meta`)</dd>
|
|
||||||
|
|
||||||
<dt>`?module`</dt>
|
|
||||||
<dd>Expands all ["bare" `import` specifiers](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier) in JavaScript modules to unpkg URLs. This feature is *very experimental*</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
### 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.
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
import { StaticRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import MainTemplate from '../client/MainTemplate';
|
import MainTemplate from '../client/MainTemplate';
|
||||||
import MainApp from '../client/main/App';
|
import MainApp from '../client/main/App';
|
||||||
|
@ -9,12 +8,9 @@ import getEntryPoints from '../utils/getEntryPoints';
|
||||||
import renderTemplate from '../utils/renderTemplate';
|
import renderTemplate from '../utils/renderTemplate';
|
||||||
|
|
||||||
export default function serveMainPage(req, res) {
|
export default function serveMainPage(req, res) {
|
||||||
const element = React.createElement(
|
const content = createHTML(
|
||||||
StaticRouter,
|
ReactDOMServer.renderToString(React.createElement(MainApp))
|
||||||
{ location: req.url },
|
|
||||||
React.createElement(MainApp)
|
|
||||||
);
|
);
|
||||||
const content = createHTML(ReactDOMServer.renderToString(element));
|
|
||||||
|
|
||||||
const entryPoints = getEntryPoints('main', {
|
const entryPoints = getEntryPoints('main', {
|
||||||
es: 'module',
|
es: 'module',
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { HashRouter } from 'react-router-dom';
|
|
||||||
import { Global, css } from '@emotion/core';
|
import { Global, css } from '@emotion/core';
|
||||||
|
|
||||||
import App from './main/App';
|
import App from './main/App';
|
||||||
|
@ -72,11 +71,9 @@ const globalStyles = css`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<HashRouter>
|
|
||||||
<div>
|
<div>
|
||||||
<Global styles={globalStyles} />
|
<Global styles={globalStyles} />
|
||||||
<App />
|
<App />
|
||||||
</div>
|
</div>,
|
||||||
</HashRouter>,
|
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Wrapper from './Wrapper';
|
|
||||||
|
|
||||||
import cloudflareLogo from './CloudflareLogo.png';
|
|
||||||
import herokuLogo from './HerokuLogo.png';
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
logoList: {
|
|
||||||
margin: '2em 0',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
textAlign: 'center',
|
|
||||||
flex: '1',
|
|
||||||
maxWidth: '80%'
|
|
||||||
},
|
|
||||||
logoImage: {
|
|
||||||
maxWidth: '60%'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function AboutLogo({ children }) {
|
|
||||||
return <div style={styles.logo}>{children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AboutLogoImage(props) {
|
|
||||||
return <img {...props} style={styles.logoImage} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function About() {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<p>
|
|
||||||
unpkg is an <a href="https://github.com/unpkg">open source</a> project
|
|
||||||
built and maintained by{' '}
|
|
||||||
<a href="https://twitter.com/mjackson">Michael Jackson</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3 id="sponsors">Sponsors</h3>
|
|
||||||
<p>
|
|
||||||
The fast, global infrastructure that powers unpkg is generously donated
|
|
||||||
by <a href="https://www.cloudflare.com">Cloudflare</a> and{' '}
|
|
||||||
<a href="https://www.heroku.com">Heroku</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div style={styles.logoList}>
|
|
||||||
<AboutLogo>
|
|
||||||
<a href="https://www.cloudflare.com">
|
|
||||||
<AboutLogoImage src={cloudflareLogo} />
|
|
||||||
</a>
|
|
||||||
</AboutLogo>
|
|
||||||
<AboutLogo>
|
|
||||||
<a href="https://www.heroku.com">
|
|
||||||
<AboutLogoImage src={herokuLogo} />
|
|
||||||
</a>
|
|
||||||
</AboutLogo>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 id="cache-behavior">Cache Behavior</h3>
|
|
||||||
<p>
|
|
||||||
The CDN caches 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.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
URLs that do not specify a package version number redirect to one that
|
|
||||||
does. This is the <code>latest</code> version when no version is
|
|
||||||
specified, or the <code>maxSatisfying</code> version when a{' '}
|
|
||||||
<a href="https://github.com/npm/node-semver">semver version</a> is
|
|
||||||
given. Redirects are cached for 5 minutes.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Browsers are instructed (via the <code>Cache-Control</code> header) to
|
|
||||||
cache assets for 1 year.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3 id="abuse">Abuse</h3>
|
|
||||||
<p>
|
|
||||||
unpkg maintains a list of packages that are known to be malicious. If
|
|
||||||
you find such a package on npm, please let us know!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3 id="support">Support</h3>
|
|
||||||
<p>
|
|
||||||
unpkg is not affiliated with or supported by npm, Inc. in any way.
|
|
||||||
Please do not contact npm for help with unpkg. Instead, please reach out
|
|
||||||
to <a href="https://twitter.com/unpkg">@unpkg</a> with any questions or
|
|
||||||
concerns.
|
|
||||||
</p>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Switch, Route, Link, withRouter } from 'react-router-dom';
|
import formatBytes from 'pretty-bytes';
|
||||||
import { Motion, spring } from 'react-motion';
|
import formatDate from 'date-fns/format';
|
||||||
|
import parseDate from 'date-fns/parse';
|
||||||
|
|
||||||
import WindowSize from './WindowSize';
|
import formatNumber from '../utils/formatNumber';
|
||||||
import About from './About';
|
import formatPercent from '../utils/formatPercent';
|
||||||
import Stats from './Stats';
|
|
||||||
import Home from './Home';
|
import cloudflareLogo from './CloudflareLogo.png';
|
||||||
|
import herokuLogo from './HerokuLogo.png';
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
title: {
|
title: {
|
||||||
|
@ -40,44 +42,60 @@ const styles = {
|
||||||
backgroundColor: 'black',
|
backgroundColor: 'black',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0
|
left: 0
|
||||||
|
},
|
||||||
|
example: {
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#eee',
|
||||||
|
margin: '2em 0',
|
||||||
|
padding: '5px 0'
|
||||||
|
},
|
||||||
|
logoList: {
|
||||||
|
margin: '2em 0',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
textAlign: 'center',
|
||||||
|
flex: '1',
|
||||||
|
maxWidth: '80%'
|
||||||
|
},
|
||||||
|
logoImage: {
|
||||||
|
maxWidth: '60%'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Layout extends React.Component {
|
function AboutLogo({ children }) {
|
||||||
state = {
|
return <div style={styles.logo}>{children}</div>;
|
||||||
underlineLeft: 0,
|
|
||||||
underlineWidth: 0,
|
|
||||||
useSpring: false,
|
|
||||||
stats: null
|
|
||||||
};
|
|
||||||
|
|
||||||
adjustUnderline = (useSpring = false) => {
|
|
||||||
let itemIndex;
|
|
||||||
switch (this.props.location.pathname) {
|
|
||||||
case '/stats':
|
|
||||||
itemIndex = 1;
|
|
||||||
break;
|
|
||||||
case '/about':
|
|
||||||
itemIndex = 2;
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
default:
|
|
||||||
itemIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemNodes = this.listNode.querySelectorAll('li');
|
function AboutLogoImage(props) {
|
||||||
const currentNode = itemNodes[itemIndex];
|
return <img {...props} style={styles.logoImage} />;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
function Stats({ data }) {
|
||||||
underlineLeft: currentNode.offsetLeft,
|
const totals = data.totals;
|
||||||
underlineWidth: currentNode.offsetWidth,
|
const since = parseDate(totals.since);
|
||||||
useSpring
|
const until = parseDate(totals.until);
|
||||||
});
|
|
||||||
};
|
return (
|
||||||
|
<p>
|
||||||
|
From <strong>{formatDate(since, 'MMM D')}</strong> to{' '}
|
||||||
|
<strong>{formatDate(until, 'MMM D')}</strong> unpkg served{' '}
|
||||||
|
<strong>{formatNumber(totals.requests.all)}</strong> requests and a total
|
||||||
|
of <strong>{formatBytes(totals.bandwidth.all)}</strong> of data to{' '}
|
||||||
|
<strong>{formatNumber(totals.uniques.all)}</strong> unique visitors,{' '}
|
||||||
|
<strong>
|
||||||
|
{formatPercent(totals.requests.cached / totals.requests.all, 0)}%
|
||||||
|
</strong>{' '}
|
||||||
|
of which were served from the cache.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class App extends React.Component {
|
||||||
|
state = { stats: null };
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.adjustUnderline();
|
|
||||||
|
|
||||||
fetch('/api/stats?period=last-month')
|
fetch('/api/stats?period=last-month')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(stats => this.setState({ stats }));
|
.then(stats => this.setState({ stats }));
|
||||||
|
@ -93,83 +111,217 @@ class Layout extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (prevProps.location.pathname !== this.props.location.pathname) {
|
|
||||||
this.adjustUnderline(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { underlineLeft, underlineWidth, useSpring } = this.state;
|
const { stats } = this.state;
|
||||||
|
|
||||||
const style = {
|
|
||||||
left: useSpring
|
|
||||||
? spring(underlineLeft, { stiffness: 220 })
|
|
||||||
: underlineLeft,
|
|
||||||
width: useSpring ? spring(underlineWidth) : underlineWidth
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="layout">
|
|
||||||
<WindowSize onChange={this.adjustUnderline} />
|
|
||||||
|
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
<header>
|
<header>
|
||||||
<h1 style={styles.title}>unpkg</h1>
|
<h1 style={styles.title}>unpkg</h1>
|
||||||
<nav style={styles.nav}>
|
|
||||||
<ol style={styles.navList} ref={node => (this.listNode = node)}>
|
<p>
|
||||||
<li style={styles.navListItem}>
|
unpkg is a fast, global{' '}
|
||||||
<Link to="/" style={styles.navLink}>
|
<a href="https://en.wikipedia.org/wiki/Content_delivery_network">
|
||||||
Home
|
content delivery network
|
||||||
</Link>
|
</a>{' '}
|
||||||
</li>
|
for everything on <a href="https://www.npmjs.com/">npm</a>. Use it
|
||||||
<li style={styles.navListItem}>
|
to quickly and easily load any file from any package using a URL
|
||||||
<Link to="/stats" style={styles.navLink}>
|
like:
|
||||||
Stats
|
</p>
|
||||||
</Link>
|
|
||||||
</li>
|
<div style={styles.example}>unpkg.com/:package@:version/:file</div>
|
||||||
<li style={styles.navListItem}>
|
|
||||||
<Link to="/about" style={styles.navLink}>
|
{stats && <Stats data={stats} />}
|
||||||
About
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<Motion
|
|
||||||
defaultStyle={{ left: underlineLeft, width: underlineWidth }}
|
|
||||||
style={style}
|
|
||||||
children={style => (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...styles.navUnderline,
|
|
||||||
WebkitTransform: `translate3d(${style.left}px,0,0)`,
|
|
||||||
transform: `translate3d(${style.left}px,0,0)`,
|
|
||||||
width: style.width
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<h3>Examples</h3>
|
||||||
|
|
||||||
|
<p>Using a fixed version:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/react@16.7.0/umd/react.production.min.js">
|
||||||
|
unpkg.com/react@16.7.0/umd/react.production.min.js
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/react-dom@16.7.0/umd/react-dom.production.min.js">
|
||||||
|
unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You may also use a{' '}
|
||||||
|
<a href="https://docs.npmjs.com/misc/semver">semver range</a> or a{' '}
|
||||||
|
<a href="https://docs.npmjs.com/cli/dist-tag">tag</a> instead of a
|
||||||
|
fixed version number, or omit the version/tag entirely to use the{' '}
|
||||||
|
<code>latest</code> tag.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/react@^16/umd/react.production.min.js">
|
||||||
|
unpkg.com/react@^16/umd/react.production.min.js
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/react/umd/react.production.min.js">
|
||||||
|
unpkg.com/react/umd/react.production.min.js
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you omit the file path (i.e. use a “bare” URL), unpkg
|
||||||
|
will serve the file specified by the <code>unpkg</code> field in{' '}
|
||||||
|
<code>package.json</code>, or fall back to
|
||||||
|
<code>main</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/d3">unpkg.com/d3</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/jquery">unpkg.com/jquery</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/three">unpkg.com/three</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Append a <code>/</code> at the end of a URL to view a listing of all
|
||||||
|
the files in a package.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/react/">unpkg.com/react/</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/lodash/">unpkg.com/lodash/</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="query-params">Query Parameters</h3>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<code>?meta</code>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
Return metadata about any file in a package as JSON (e.g.
|
||||||
|
<code>/any/file?meta</code>)
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>
|
||||||
|
<code>?module</code>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
Expands all{' '}
|
||||||
|
<a href="https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier">
|
||||||
|
“bare” <code>import</code> specifiers
|
||||||
|
</a>{' '}
|
||||||
|
in JavaScript modules to unpkg URLs. This feature is{' '}
|
||||||
|
<em>very experimental</em>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3 id="cache-behavior">Cache Behavior</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The CDN caches 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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
URLs that do not specify a package version number redirect to one that
|
||||||
|
does. This is the <code>latest</code> version when no version is
|
||||||
|
specified, or the <code>maxSatisfying</code> version when a{' '}
|
||||||
|
<a href="https://github.com/npm/node-semver">semver version</a> is
|
||||||
|
given. Redirects are cached for 5 minutes.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Browsers are instructed (via the <code>Cache-Control</code> header) to
|
||||||
|
cache assets for 1 year.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="about">About</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
unpkg is an <a href="https://github.com/unpkg">open source</a> project
|
||||||
|
built and maintained by{' '}
|
||||||
|
<a href="https://twitter.com/mjackson">Michael Jackson</a>. unpkg is
|
||||||
|
not affiliated with or supported by npm, Inc. in any way. Please do
|
||||||
|
not contact npm for help with unpkg. Instead, please reach out to{' '}
|
||||||
|
<a href="https://twitter.com/unpkg">@unpkg</a> with any questions or
|
||||||
|
concerns.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The fast, global infrastructure that powers unpkg is generously
|
||||||
|
donated by <a href="https://www.cloudflare.com">Cloudflare</a> and{' '}
|
||||||
|
<a href="https://www.heroku.com">Heroku</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style={styles.logoList}>
|
||||||
|
<AboutLogo>
|
||||||
|
<a href="https://www.cloudflare.com">
|
||||||
|
<AboutLogoImage src={cloudflareLogo} />
|
||||||
|
</a>
|
||||||
|
</AboutLogo>
|
||||||
|
<AboutLogo>
|
||||||
|
<a href="https://www.heroku.com">
|
||||||
|
<AboutLogoImage src={herokuLogo} />
|
||||||
|
</a>
|
||||||
|
</AboutLogo>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<h3 id="workflow">Workflow</h3>
|
||||||
<Route
|
|
||||||
path="/stats"
|
<p>
|
||||||
render={() => <Stats data={this.state.stats} />}
|
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
|
||||||
<Route path="/about" component={About} />
|
include your <a href="https://github.com/umdjs/umd">UMD</a> build in
|
||||||
<Route path="/" component={Home} />
|
your npm package (not your repo, that's different!).
|
||||||
</Switch>
|
</p>
|
||||||
|
|
||||||
|
<p>You can do this easily using the following setup:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Add the <code>umd</code> (or <code>dist</code>) directory to your{' '}
|
||||||
|
<code>.gitignore</code> file
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Add the <code>umd</code> directory to your{' '}
|
||||||
|
<a href="https://docs.npmjs.com/files/package.json#files">
|
||||||
|
files array
|
||||||
|
</a>{' '}
|
||||||
|
in
|
||||||
|
<code>package.json</code>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Use a build script to generate your UMD build in the{' '}
|
||||||
|
<code>umd</code> directory when you publish
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
That's it! Now when you <code>npm publish</code> you'll have
|
||||||
|
a version available on unpkg as well.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
Layout.propTypes = {
|
App.propTypes = {
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
children: PropTypes.node
|
children: PropTypes.node
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(Layout);
|
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Wrapper from './Wrapper';
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
homeExample: {
|
|
||||||
textAlign: 'center',
|
|
||||||
backgroundColor: '#eee',
|
|
||||||
margin: '2em 0',
|
|
||||||
padding: '5px 0'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<p>
|
|
||||||
unpkg is a fast, global{' '}
|
|
||||||
<a href="https://en.wikipedia.org/wiki/Content_delivery_network">
|
|
||||||
content delivery network
|
|
||||||
</a>{' '}
|
|
||||||
for everything on <a href="https://www.npmjs.com/">npm</a>. Use it to
|
|
||||||
quickly and easily load any file from any package using a URL like:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div style={styles.homeExample}>unpkg.com/:package@:version/:file</div>
|
|
||||||
|
|
||||||
<h3>Examples</h3>
|
|
||||||
|
|
||||||
<p>Using a fixed version:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/react@16.0.0/umd/react.production.min.js">
|
|
||||||
unpkg.com/react@16.0.0/umd/react.production.min.js
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/react-dom@16.0.0/umd/react-dom.production.min.js">
|
|
||||||
unpkg.com/react-dom@16.0.0/umd/react-dom.production.min.js
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You may also use a{' '}
|
|
||||||
<a href="https://docs.npmjs.com/misc/semver">semver range</a> or a{' '}
|
|
||||||
<a href="https://docs.npmjs.com/cli/dist-tag">tag</a> instead of a fixed
|
|
||||||
version number, or omit the version/tag entirely to use the{' '}
|
|
||||||
<code>latest</code> tag.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/react@^16/umd/react.production.min.js">
|
|
||||||
unpkg.com/react@^16/umd/react.production.min.js
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/react/umd/react.production.min.js">
|
|
||||||
unpkg.com/react/umd/react.production.min.js
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If you omit the file path (i.e. use a “bare” URL), unpkg
|
|
||||||
will serve the file specified by the <code>unpkg</code> field in{' '}
|
|
||||||
<code>package.json</code>, or fall back to
|
|
||||||
<code>main</code>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/d3">unpkg.com/d3</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/jquery">unpkg.com/jquery</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/three">unpkg.com/three</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Append a <code>/</code> at the end of a URL to view a listing of all the
|
|
||||||
files in a package.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/react/">unpkg.com/react/</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/lodash/">unpkg.com/lodash/</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Query Parameters</h3>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<code>?meta</code>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
Return metadata about any file in a package as JSON (e.g.
|
|
||||||
<code>/any/file?meta</code>)
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt>
|
|
||||||
<code>?module</code>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
Expands all{' '}
|
|
||||||
<a href="https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier">
|
|
||||||
“bare” <code>import</code> specifiers
|
|
||||||
</a>
|
|
||||||
in JavaScript modules to unpkg URLs. This feature is{' '}
|
|
||||||
<em>very experimental</em>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h3>Workflow</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
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 <a href="https://github.com/umdjs/umd">UMD</a> build in
|
|
||||||
your npm package (not your repo, that's different!).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>You can do this easily using the following setup:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Add the <code>umd</code> (or <code>dist</code>) directory to your{' '}
|
|
||||||
<code>.gitignore</code> file
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Add the <code>umd</code> directory to your{' '}
|
|
||||||
<a href="https://docs.npmjs.com/files/package.json#files">
|
|
||||||
files array
|
|
||||||
</a>{' '}
|
|
||||||
in
|
|
||||||
<code>package.json</code>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Use a build script to generate your UMD build in the <code>umd</code>{' '}
|
|
||||||
directory when you publish
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
That's it! Now when you <code>npm publish</code> you'll have a
|
|
||||||
version available on unpkg as well.
|
|
||||||
</p>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,337 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import formatBytes from 'pretty-bytes';
|
|
||||||
import formatDate from 'date-fns/format';
|
|
||||||
import parseDate from 'date-fns/parse';
|
|
||||||
|
|
||||||
import { continents, countries } from './countries.json';
|
|
||||||
|
|
||||||
import Wrapper from './Wrapper';
|
|
||||||
import formatNumber from '../utils/formatNumber';
|
|
||||||
import formatPercent from '../utils/formatPercent';
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
tableFilter: {
|
|
||||||
fontSize: '0.8em',
|
|
||||||
textAlign: 'right'
|
|
||||||
},
|
|
||||||
countryName: {
|
|
||||||
paddingLeft: 20
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCountriesByContinent(continent) {
|
|
||||||
return Object.keys(countries).filter(
|
|
||||||
country => countries[country].continent === continent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sumKeyValues(hash, keys) {
|
|
||||||
return keys.reduce((n, key) => n + (hash[key] || 0), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// function sumValues(hash) {
|
|
||||||
// return Object.keys(hash).reduce((memo, key) => memo + hash[key], 0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default class Stats extends React.Component {
|
|
||||||
state = {
|
|
||||||
// minPackageRequests: 1000000,
|
|
||||||
minCountryRequests: 1000000
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { data } = this.props;
|
|
||||||
|
|
||||||
if (data == null) return null;
|
|
||||||
|
|
||||||
const totals = data.totals;
|
|
||||||
|
|
||||||
// Summary data
|
|
||||||
const since = parseDate(totals.since);
|
|
||||||
const until = parseDate(totals.until);
|
|
||||||
|
|
||||||
// Packages
|
|
||||||
// const packageRows = [];
|
|
||||||
|
|
||||||
// Object.keys(totals.requests.package)
|
|
||||||
// .sort((a, b) => {
|
|
||||||
// return totals.requests.package[b] - totals.requests.package[a];
|
|
||||||
// })
|
|
||||||
// .forEach(packageName => {
|
|
||||||
// const requests = totals.requests.package[packageName];
|
|
||||||
// const bandwidth = totals.bandwidth.package[packageName];
|
|
||||||
|
|
||||||
// if (requests >= this.state.minPackageRequests) {
|
|
||||||
// packageRows.push(
|
|
||||||
// <tr key={packageName}>
|
|
||||||
// <td>
|
|
||||||
// <a
|
|
||||||
// href={`https://npmjs.org/package/${packageName}`}
|
|
||||||
// title={`${packageName} on npm`}
|
|
||||||
// >
|
|
||||||
// {packageName}
|
|
||||||
// </a>
|
|
||||||
// </td>
|
|
||||||
// <td>
|
|
||||||
// {formatNumber(requests)} (
|
|
||||||
// {formatPercent(requests / totals.requests.all)}
|
|
||||||
// %)
|
|
||||||
// </td>
|
|
||||||
// {bandwidth ? (
|
|
||||||
// <td>
|
|
||||||
// {formatBytes(bandwidth)} (
|
|
||||||
// {formatPercent(bandwidth / totals.bandwidth.all)}
|
|
||||||
// %)
|
|
||||||
// </td>
|
|
||||||
// ) : (
|
|
||||||
// <td>-</td>
|
|
||||||
// )}
|
|
||||||
// </tr>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Regions
|
|
||||||
const regionRows = [];
|
|
||||||
|
|
||||||
const continentsData = Object.keys(continents).reduce((memo, continent) => {
|
|
||||||
const localCountries = getCountriesByContinent(continent);
|
|
||||||
|
|
||||||
memo[continent] = {
|
|
||||||
countries: localCountries,
|
|
||||||
requests: sumKeyValues(totals.requests.country, localCountries),
|
|
||||||
bandwidth: sumKeyValues(totals.bandwidth.country, localCountries)
|
|
||||||
};
|
|
||||||
|
|
||||||
return memo;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const topContinents = Object.keys(continentsData).sort((a, b) => {
|
|
||||||
return continentsData[b].requests - continentsData[a].requests;
|
|
||||||
});
|
|
||||||
|
|
||||||
topContinents.forEach(continent => {
|
|
||||||
const continentName = continents[continent];
|
|
||||||
const continentData = continentsData[continent];
|
|
||||||
|
|
||||||
if (
|
|
||||||
continentData.requests > this.state.minCountryRequests &&
|
|
||||||
continentData.bandwidth !== 0
|
|
||||||
) {
|
|
||||||
regionRows.push(
|
|
||||||
<tr key={continent}>
|
|
||||||
<td>
|
|
||||||
<strong>{continentName}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>
|
|
||||||
{formatNumber(continentData.requests)} (
|
|
||||||
{formatPercent(continentData.requests / totals.requests.all)}
|
|
||||||
%)
|
|
||||||
</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>
|
|
||||||
{formatBytes(continentData.bandwidth)} (
|
|
||||||
{formatPercent(continentData.bandwidth / totals.bandwidth.all)}
|
|
||||||
%)
|
|
||||||
</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
|
|
||||||
const topCountries = continentData.countries.sort((a, b) => {
|
|
||||||
return totals.requests.country[b] - totals.requests.country[a];
|
|
||||||
});
|
|
||||||
|
|
||||||
topCountries.forEach(country => {
|
|
||||||
const countryRequests = totals.requests.country[country];
|
|
||||||
const countryBandwidth = totals.bandwidth.country[country];
|
|
||||||
|
|
||||||
if (countryRequests > this.state.minCountryRequests) {
|
|
||||||
regionRows.push(
|
|
||||||
<tr key={continent + country}>
|
|
||||||
<td style={styles.countryName}>{countries[country].name}</td>
|
|
||||||
<td>
|
|
||||||
{formatNumber(countryRequests)} (
|
|
||||||
{formatPercent(countryRequests / totals.requests.all)}
|
|
||||||
%)
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{formatBytes(countryBandwidth)} (
|
|
||||||
{formatPercent(countryBandwidth / totals.bandwidth.all)}
|
|
||||||
%)
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Protocols
|
|
||||||
// const protocolRows = Object.keys(totals.requests.protocol)
|
|
||||||
// .sort((a, b) => {
|
|
||||||
// return totals.requests.protocol[b] - totals.requests.protocol[a];
|
|
||||||
// })
|
|
||||||
// .map(protocol => {
|
|
||||||
// const requests = totals.requests.protocol[protocol];
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <tr key={protocol}>
|
|
||||||
// <td>{protocol}</td>
|
|
||||||
// <td>
|
|
||||||
// {formatNumber(requests)} (
|
|
||||||
// {formatPercent(requests / sumValues(totals.requests.protocol))}
|
|
||||||
// %)
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<p>
|
|
||||||
From <strong>{formatDate(since, 'MMM D')}</strong> to{' '}
|
|
||||||
<strong>{formatDate(until, 'MMM D')}</strong> unpkg served{' '}
|
|
||||||
<strong>{formatNumber(totals.requests.all)}</strong> requests and a
|
|
||||||
total of <strong>{formatBytes(totals.bandwidth.all)}</strong> of data
|
|
||||||
to <strong>{formatNumber(totals.uniques.all)}</strong> unique
|
|
||||||
visitors,{' '}
|
|
||||||
<strong>
|
|
||||||
{formatPercent(totals.requests.cached / totals.requests.all, 0)}%
|
|
||||||
</strong>{' '}
|
|
||||||
of which were served from the cache.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Packages</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We recently migrated unpkg to a new backend and are working on getting
|
|
||||||
package-specific data back on the site.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<p>
|
|
||||||
The table below shows the most popular packages served by unpkg from{' '}
|
|
||||||
<strong>{formatDate(since, 'MMM D')}</strong> to{' '}
|
|
||||||
<strong>{formatDate(until, 'MMM D')}</strong>. Only the top{' '}
|
|
||||||
{Object.keys(totals.requests.package).length} packages are shown.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style={styles.tableFilter}>
|
|
||||||
Include only packages that received at least{' '}
|
|
||||||
<select
|
|
||||||
value={this.state.minPackageRequests}
|
|
||||||
onChange={event =>
|
|
||||||
this.setState({
|
|
||||||
minPackageRequests: parseInt(event.target.value, 10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<option value="0">0</option>
|
|
||||||
<option value="1000">1,000</option>
|
|
||||||
<option value="10000">10,000</option>
|
|
||||||
<option value="100000">100,000</option>
|
|
||||||
<option value="1000000">1,000,000</option>
|
|
||||||
<option value="10000000">10,000,000</option>
|
|
||||||
</select>{' '}
|
|
||||||
requests.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table cellSpacing="0" cellPadding="0" style={{ width: '100%' }}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<strong>Package</strong>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<strong>Requests (% of total)</strong>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<strong>Bandwidth (% of total)</strong>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>{packageRows}</tbody>
|
|
||||||
</table>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<h3>Regions</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The table below breaks down requests to unpkg from{' '}
|
|
||||||
<strong>{formatDate(since, 'MMM D')}</strong> to{' '}
|
|
||||||
<strong>{formatDate(until, 'MMM D')}</strong> by geographic region.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style={styles.tableFilter}>
|
|
||||||
Include only countries that made at least{' '}
|
|
||||||
<select
|
|
||||||
value={this.state.minCountryRequests}
|
|
||||||
onChange={event =>
|
|
||||||
this.setState({
|
|
||||||
minCountryRequests: parseInt(event.target.value, 10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<option value="0">0</option>
|
|
||||||
<option value="100000">100,000</option>
|
|
||||||
<option value="1000000">1,000,000</option>
|
|
||||||
<option value="10000000">10,000,000</option>
|
|
||||||
<option value="100000000">100,000,000</option>
|
|
||||||
</select>{' '}
|
|
||||||
requests.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table cellSpacing="0" cellPadding="0" style={{ width: '100%' }}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<strong>Region</strong>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<strong>Requests (% of total)</strong>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<strong>Bandwidth (% of total)</strong>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>{regionRows}</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<h3>Protocols</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The table below breaks down requests to unpkg from{' '}
|
|
||||||
<strong>{formatDate(since, 'MMM D')}</strong> to{' '}
|
|
||||||
<strong>{formatDate(until, 'MMM D')}</strong> by HTTP protocol.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table cellSpacing="0" cellPadding="0" style={{ width: '100%' }}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<strong>Protocol</strong>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<strong>Requests (% of total)</strong>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>{protocolRows}</tbody>
|
|
||||||
</table>
|
|
||||||
*/}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
Stats.propTypes = {
|
|
||||||
data: PropTypes.object
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { addEvent, removeEvent } from '../utils/dom';
|
|
||||||
|
|
||||||
const resizeEvent = 'resize';
|
|
||||||
|
|
||||||
export default class WindowSize extends React.Component {
|
|
||||||
handleWindowResize = () => {
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange({
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
addEvent(window, resizeEvent, this.handleWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
removeEvent(window, resizeEvent, this.handleWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
WindowSize.propTypes = {
|
|
||||||
onChange: PropTypes.func
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function Wrapper({ children }) {
|
|
||||||
return <div style={{ maxWidth: 700, margin: '0 auto' }}>{children}</div>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,15 +0,0 @@
|
||||||
export function addEvent(node, type, handler) {
|
|
||||||
if (node.addEventListener) {
|
|
||||||
node.addEventListener(type, handler, false);
|
|
||||||
} else if (node.attachEvent) {
|
|
||||||
node.attachEvent('on' + type, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeEvent(node, type, handler) {
|
|
||||||
if (node.removeEventListener) {
|
|
||||||
node.removeEventListener(type, handler, false);
|
|
||||||
} else if (node.detachEvent) {
|
|
||||||
node.detachEvent('on' + type, handler);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export default function parseNumber(s) {
|
|
||||||
return parseInt(s.replace(/,/g, ''), 10) || 0;
|
|
||||||
}
|
|
|
@ -2437,12 +2437,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"countries-list": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/countries-list/-/countries-list-1.4.1.tgz",
|
|
||||||
"integrity": "sha1-R+JU/WD1NmDUstGUrhF/qTjqfCk=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||||
|
@ -4232,33 +4226,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"history": {
|
|
||||||
"version": "4.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
|
|
||||||
"integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
|
|
||||||
"requires": {
|
|
||||||
"invariant": "^2.2.1",
|
|
||||||
"loose-envify": "^1.2.0",
|
|
||||||
"resolve-pathname": "^2.2.0",
|
|
||||||
"value-equal": "^0.4.0",
|
|
||||||
"warning": "^3.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"warning": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hoist-non-react-statics": {
|
|
||||||
"version": "2.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
|
||||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
|
||||||
},
|
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
|
||||||
|
@ -4399,6 +4366,7 @@
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -6117,21 +6085,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
|
||||||
"version": "1.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
|
|
||||||
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
|
|
||||||
"requires": {
|
|
||||||
"isarray": "0.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"path-type": {
|
"path-type": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
|
||||||
|
@ -6356,33 +6309,6 @@
|
||||||
"raf": "^3.1.0"
|
"raf": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-router": {
|
|
||||||
"version": "4.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
|
|
||||||
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
|
|
||||||
"requires": {
|
|
||||||
"history": "^4.7.2",
|
|
||||||
"hoist-non-react-statics": "^2.5.0",
|
|
||||||
"invariant": "^2.2.4",
|
|
||||||
"loose-envify": "^1.3.1",
|
|
||||||
"path-to-regexp": "^1.7.0",
|
|
||||||
"prop-types": "^15.6.1",
|
|
||||||
"warning": "^4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-router-dom": {
|
|
||||||
"version": "4.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
|
|
||||||
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
|
|
||||||
"requires": {
|
|
||||||
"history": "^4.7.2",
|
|
||||||
"invariant": "^2.2.4",
|
|
||||||
"loose-envify": "^1.3.1",
|
|
||||||
"prop-types": "^15.6.1",
|
|
||||||
"react-router": "^4.3.1",
|
|
||||||
"warning": "^4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||||
|
@ -6678,11 +6604,6 @@
|
||||||
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
|
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"resolve-pathname": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
|
|
||||||
},
|
|
||||||
"resolve-url": {
|
"resolve-url": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||||
|
@ -8079,11 +8000,6 @@
|
||||||
"spdx-expression-parse": "^3.0.0"
|
"spdx-expression-parse": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"value-equal": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
|
|
||||||
},
|
|
||||||
"verror": {
|
"verror": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
@ -8113,14 +8029,6 @@
|
||||||
"makeerror": "1.0.x"
|
"makeerror": "1.0.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warning": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"watch": {
|
"watch": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz",
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"react": "^16.7.0",
|
"react": "^16.7.0",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-motion": "^0.5.2",
|
"react-motion": "^0.5.2",
|
||||||
"react-router-dom": "^4.3.1",
|
|
||||||
"sort-by": "^1.2.0"
|
"sort-by": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -29,7 +28,6 @@
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-eslint": "^8.0.3",
|
"babel-eslint": "^8.0.3",
|
||||||
"babel-jest": "^23.4.2",
|
"babel-jest": "^23.4.2",
|
||||||
"countries-list": "^1.4.1",
|
|
||||||
"dotenv": "^6.2.0",
|
"dotenv": "^6.2.0",
|
||||||
"eslint": "^4.13.1",
|
"eslint": "^4.13.1",
|
||||||
"eslint-import-resolver-node": "^0.3.2",
|
"eslint-import-resolver-node": "^0.3.2",
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
const countriesList = require('countries-list');
|
|
||||||
|
|
||||||
const json = JSON.stringify({
|
|
||||||
continents: countriesList.continents,
|
|
||||||
countries: Object.keys(countriesList.countries).reduce((memo, key) => {
|
|
||||||
const { name, continent } = countriesList.countries[key];
|
|
||||||
memo[key] = { name, continent };
|
|
||||||
return memo;
|
|
||||||
}, {})
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(json);
|
|
Loading…
Reference in New Issue