/** @jsx jsx */ import { Global, css, jsx } from '@emotion/core'; // import { Fragment, useEffect, useState } from 'react'; import { Fragment, useState } 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 { formatNumber, formatPercent } from '../utils/format.js'; import { fontSans, fontMono } from '../utils/style.js'; import { TwitterIcon, GitHubIcon } from './Icons.js'; import CloudflareLogo from './images/CloudflareLogo.png'; import AngularLogo from './images/AngularLogo.png'; const globalStyles = css` html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } html, body, #root { height: 100%; margin: 0; } body { ${fontSans} font-size: 16px; line-height: 1.5; background: white; color: black; } code { ${fontMono} } dd, ul { margin-left: 0; padding-left: 25px; } #root { display: flex; flex-direction: column; } `; const linkStyle = { color: '#0076ff', textDecoration: 'none', ':hover': { textDecoration: 'underline' } }; function AboutLogo({ children }) { return <div css={{ textAlign: 'center', flex: '1' }}>{children}</div>; } function AboutLogoImage(props) { return <img {...props} css={{ maxWidth: '90%' }} />; } function Stats({ data }) { const totals = data.totals; const since = parseDate(totals.since); 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, 2)}% </strong>{' '} of which were served from the cache. </p> ); } export default function App() { // const [stats, setStats] = useState( const [stats] = useState( typeof window === 'object' && window.localStorage && window.localStorage.savedStats ? JSON.parse(window.localStorage.savedStats) : null ); const hasStats = !!(stats && !stats.error); // const stringStats = JSON.stringify(stats); // useEffect(() => { // window.localStorage.savedStats = stringStats; // }, [stringStats]); // useEffect(() => { // fetch('/api/stats?period=last-month') // .then(res => res.json()) // .then(setStats); // }, []); return ( <Fragment> <div css={{ maxWidth: 740, margin: '0 auto', padding: '0 20px' }} > <Global styles={globalStyles} /> <header> <h1 css={{ textTransform: 'uppercase', textAlign: 'center', fontSize: '5em' }} > unpkg </h1> <p> unpkg is a fast, global content delivery network for everything on{' '} <a href="https://www.npmjs.com/" css={linkStyle}> npm </a> . Use it to quickly and easily load any file from any package using a URL like: </p> <div css={{ textAlign: 'center', backgroundColor: '#eee', margin: '2em 0', padding: '5px 0' }} > unpkg.com/:package@:version/:file </div> {hasStats && <Stats data={stats} />} </header> <h3 css={{ fontSize: '1.6em' }} id="examples"> Examples </h3> <p>Using a fixed version:</p> <ul> <li> <a title="react.production.min.js" href="/react@16.7.0/umd/react.production.min.js" css={linkStyle} > unpkg.com/react@16.7.0/umd/react.production.min.js </a> </li> <li> <a title="react-dom.production.min.js" href="/react-dom@16.7.0/umd/react-dom.production.min.js" css={linkStyle} > unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js </a> </li> </ul> <p> You may also use a{' '} <a title="semver" href="https://docs.npmjs.com/misc/semver" css={linkStyle} > semver range </a>{' '} or a{' '} <a title="tags" href="https://docs.npmjs.com/cli/dist-tag" css={linkStyle} > 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 title="react.production.min.js" href="/react@^16/umd/react.production.min.js" css={linkStyle} > unpkg.com/react@^16/umd/react.production.min.js </a> </li> <li> <a title="react.production.min.js" href="/react/umd/react.production.min.js" css={linkStyle} > 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 title="jQuery" href="/jquery" css={linkStyle}> unpkg.com/jquery </a> </li> <li> <a title="Three.js" href="/three" css={linkStyle}> 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 title="Index of the react package" href="/react/" css={linkStyle} > unpkg.com/react/ </a> </li> <li> <a title="Index of the react-router package" href="/react-router/" css={linkStyle} > unpkg.com/react-router/ </a> </li> </ul> <h3 css={{ fontSize: '1.6em' }} 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 title="bare import specifiers" href="https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier" css={linkStyle} > “bare” <code>import</code> specifiers </a>{' '} in JavaScript modules to unpkg URLs. This feature is{' '} <em>very experimental</em> </dd> </dl> <h3 css={{ fontSize: '1.6em' }} 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> Browsers are instructed (via the <code>Cache-Control</code> header) to cache assets indefinitely (1 year). </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 title="semver" href="https://github.com/npm/node-semver" css={linkStyle} > semver version </a>{' '} is given. Redirects are cached for 10 minutes at the CDN, 1 minute in browsers. </p> <p> If you want users to be able to use the latest version when you cut a new release, the best policy is to put the version number in the URL directly in your installation instructions. This will also load more quickly because we won't have to resolve the latest version and redirect them. </p> <h3 css={{ fontSize: '1.6em' }} id="workflow"> 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 title="UMD" href="https://github.com/umdjs/umd" css={linkStyle}> 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 title="package.json files array" href="https://docs.npmjs.com/files/package.json#files" css={linkStyle} > 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> <h3 css={{ fontSize: '1.6em' }} id="about"> About </h3> <p> unpkg is an{' '} <a title="unpkg on GitHub" href="https://github.com/unpkg" css={linkStyle} > open source </a>{' '} project built and maintained by{' '} <a title="mjackson on Twitter" href="https://twitter.com/mjackson" css={linkStyle} > 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 title="unpkg on Twitter" href="https://twitter.com/unpkg" css={linkStyle} > @unpkg </a>{' '} with any questions or concerns. </p> <p> The unpkg CDN is powered by{' '} <a title="Cloudflare" href="https://www.cloudflare.com" css={linkStyle} > Cloudflare </a> , one of the world's largest and fastest cloud network platforms.{' '} {hasStats && ( <span> In the past month, Cloudflare served over{' '} <strong>{formatBytes(stats.totals.bandwidth.all)}</strong> to{' '} <strong>{formatNumber(stats.totals.uniques.all)}</strong> unique unpkg users all over the world. </span> )} </p> <div css={{ margin: '4em 0', display: 'flex', justifyContent: 'center' }} > <AboutLogo> <a title="Cloudflare" href="https://www.cloudflare.com"> <AboutLogoImage src={CloudflareLogo} height="100" /> </a> </AboutLogo> </div> <p> The origin servers for unpkg are powered by{' '} <a title="Google Cloud" href="https://cloud.google.com/" css={linkStyle} > Google Cloud </a>{' '} and made possible by a generous donation from the{' '} <a title="Angular" href="https://angular.io" css={linkStyle}> Angular web framework </a> , one of the world's most popular libraries for building incredible user experiences on both desktop and mobile. </p> <div css={{ margin: '4em 0 0', display: 'flex', justifyContent: 'center' }} > <AboutLogo> <a title="Angular" href="https://angular.io"> <AboutLogoImage src={AngularLogo} width="200" /> </a> </AboutLogo> </div> </div> <footer css={{ marginTop: '5rem', background: 'black', color: '#aaa' }} > <div css={{ maxWidth: 740, padding: '10px 20px', margin: '0 auto', display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }} > <p>© {new Date().getFullYear()} UNPKG</p> <p css={{ fontSize: '1.5rem' }}> <a title="Twitter" href="https://twitter.com/unpkg" css={{ color: '#aaa', display: 'inline-block', ':hover': { color: 'white' } }} > <TwitterIcon /> </a> <a title="GitHub" href="https://github.com/mjackson/unpkg" css={{ color: '#aaa', display: 'inline-block', marginLeft: '1rem', ':hover': { color: 'white' } }} > <GitHubIcon /> </a> </p> </div> </footer> </Fragment> ); } if (process.env.NODE_ENV !== 'production') { App.propTypes = { location: PropTypes.object, children: PropTypes.node }; }