Server render auto-index pages
Also, load the autoIndex bundle on the client so we can use React instead of using an inline script.
This commit is contained in:
parent
168ccf5aac
commit
135da0fdc5
client
docker-compose.ymlpackage.jsonserver.jsserver
actions
serveAutoIndexPage.jsserveFile.jsserveJavaScriptModule.jsserveMainPage.jsserveMetadata.jsserveStaticFile.js
client
.babelrc.eslintrcMainPage.jsautoIndex.cssautoIndex.js
clientRuntime.jsautoIndex
main.cssmain.jsmain
About.cssAbout.jsAbout.mdApp.jsCloudflareLogo.pngHerokuLogo.pngHome.cssHome.jsHome.mdLayout.cssLayout.jsStats.cssStats.jsWindowSize.js
utils
components
utils
|
@ -1,8 +0,0 @@
|
|||
import "./main.css";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import App from "./main/App";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
|
@ -1,11 +0,0 @@
|
|||
import "./About.css";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import html from "./About.md";
|
||||
|
||||
function About() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
||||
|
||||
export default About;
|
|
@ -1,11 +0,0 @@
|
|||
import React from "react";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import Layout from "./Layout";
|
||||
|
||||
const App = () => (
|
||||
<HashRouter>
|
||||
<Layout />
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
export default App;
|
|
@ -1,11 +0,0 @@
|
|||
import "./Home.css";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import html from "./Home.md";
|
||||
|
||||
function Home() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,10 +0,0 @@
|
|||
const formatNumber = n => {
|
||||
const digits = String(n).split("");
|
||||
const groups = [];
|
||||
|
||||
while (digits.length) groups.unshift(digits.splice(-3).join(""));
|
||||
|
||||
return groups.join(",");
|
||||
};
|
||||
|
||||
export default formatNumber;
|
|
@ -1,4 +0,0 @@
|
|||
const formatPercent = (n, fixed = 1) =>
|
||||
String((n.toPrecision(2) * 100).toFixed(fixed));
|
||||
|
||||
export default formatPercent;
|
|
@ -1,3 +0,0 @@
|
|||
const parseNumber = s => parseInt(s.replace(/,/g, ""), 10) || 0;
|
||||
|
||||
export default parseNumber;
|
|
@ -12,7 +12,7 @@ services:
|
|||
|
||||
server:
|
||||
build: .
|
||||
command: node_modules/.bin/nodemon --ignore client server.js
|
||||
command: node_modules/.bin/nodemon --ignore server/client server.js
|
||||
env_file: .env
|
||||
environment:
|
||||
- CACHE_URL=redis://data:6379
|
||||
|
@ -30,7 +30,7 @@ services:
|
|||
|
||||
worker:
|
||||
build: .
|
||||
command: node_modules/.bin/nodemon --ignore client server/ingestLogs.js
|
||||
command: node_modules/.bin/nodemon --ignore server/client server/ingestLogs.js
|
||||
env_file: .env
|
||||
environment:
|
||||
- DATA_URL=redis://data:6379
|
||||
|
|
13
package.json
13
package.json
|
@ -7,12 +7,16 @@
|
|||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-plugin-syntax-export-extensions": "^6.13.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.14",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"cors": "^2.8.1",
|
||||
"countries-list": "^1.3.2",
|
||||
"csso": "^3.1.1",
|
||||
"date-fns": "^1.28.1",
|
||||
"etag": "^1.8.0",
|
||||
"express": "^4.15.2",
|
||||
|
@ -44,15 +48,12 @@
|
|||
"devDependencies": {
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"css-loader": "0.26.1",
|
||||
"errorhandler": "^1.5.0",
|
||||
"eslint": "^4.13.1",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-react": "^7.5.1",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "0.10.0",
|
||||
"html-loader": "^0.5.1",
|
||||
"jest": "^22.4.4",
|
||||
|
|
|
@ -6,6 +6,8 @@ const createServer = require("./server/createServer");
|
|||
const createDevServer = require("./server/createDevServer");
|
||||
const config = require("./server/config");
|
||||
|
||||
require("./server/clientRuntime");
|
||||
|
||||
if (process.env.SENTRY_DSN) {
|
||||
raven
|
||||
.config(process.env.SENTRY_DSN, {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
const React = require("react");
|
||||
const ReactDOMServer = require("react-dom/server");
|
||||
const semver = require("semver");
|
||||
|
||||
const MainPage = require("../client/MainPage");
|
||||
const AutoIndexApp = require("../client/autoIndex/App");
|
||||
const renderPage = require("../utils/renderPage");
|
||||
|
||||
const globalScripts =
|
||||
process.env.NODE_ENV === "production"
|
||||
? [
|
||||
"/react@16.4.1/umd/react.production.min.js",
|
||||
"/react-dom@16.4.1/umd/react-dom.production.min.js",
|
||||
"/react-router-dom@4.3.1/umd/react-router-dom.min.js"
|
||||
]
|
||||
: [
|
||||
"/react@16.4.1/umd/react.development.js",
|
||||
"/react-dom@16.4.1/umd/react-dom.development.js",
|
||||
"/react-router-dom@4.3.1/umd/react-router-dom.js"
|
||||
];
|
||||
|
||||
function byVersion(a, b) {
|
||||
return semver.lt(a, b) ? -1 : semver.gt(a, b) ? 1 : 0;
|
||||
}
|
||||
|
||||
function serveAutoIndexPage(req, res) {
|
||||
const scripts = globalScripts.concat(req.assets.getScripts("autoIndex"));
|
||||
const styles = req.assets.getStyles("autoIndex");
|
||||
|
||||
const props = {
|
||||
packageName: req.packageName,
|
||||
packageVersion: req.packageVersion,
|
||||
availableVersions: Object.keys(req.packageInfo.versions).sort(byVersion),
|
||||
filename: req.filename,
|
||||
entry: req.entry,
|
||||
entries: req.entries
|
||||
};
|
||||
const content = ReactDOMServer.renderToString(
|
||||
React.createElement(AutoIndexApp, props)
|
||||
);
|
||||
|
||||
const html = renderPage(MainPage, {
|
||||
scripts: scripts,
|
||||
styles: styles,
|
||||
data: props,
|
||||
content: content
|
||||
});
|
||||
|
||||
res
|
||||
.set({
|
||||
"Cache-Control": "public,max-age=60", // 1 minute
|
||||
"Cache-Tag": "auto-index"
|
||||
})
|
||||
.send(html);
|
||||
}
|
||||
|
||||
module.exports = serveAutoIndexPage;
|
|
@ -1,155 +1,7 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const etag = require("etag");
|
||||
const babel = require("babel-core");
|
||||
|
||||
const IndexPage = require("../components/IndexPage");
|
||||
const unpkgRewrite = require("../plugins/unpkgRewrite");
|
||||
const addLeadingSlash = require("../utils/addLeadingSlash");
|
||||
const renderPage = require("../utils/renderPage");
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || ".";
|
||||
|
||||
return Object.keys(entries)
|
||||
.filter(name => entry.name !== name && path.dirname(name) === dirname)
|
||||
.map(name => entries[name]);
|
||||
}
|
||||
|
||||
function getMetadata(entry, entries) {
|
||||
const metadata = {
|
||||
path: addLeadingSlash(entry.name),
|
||||
type: entry.type
|
||||
};
|
||||
|
||||
if (entry.type === "file") {
|
||||
metadata.contentType = entry.contentType;
|
||||
metadata.integrity = entry.integrity;
|
||||
metadata.lastModified = entry.lastModified;
|
||||
metadata.size = entry.size;
|
||||
} else if (entry.type === "directory") {
|
||||
metadata.files = getMatchingEntries(entry, entries).map(e =>
|
||||
getMetadata(e, entries)
|
||||
);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
function serveMetadata(req, res) {
|
||||
const metadata = getMetadata(req.entry, req.entries);
|
||||
|
||||
res
|
||||
.set({
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
"Cache-Tag": "meta"
|
||||
})
|
||||
.send(metadata);
|
||||
}
|
||||
|
||||
function rewriteBareModuleIdentifiers(code, packageConfig) {
|
||||
const dependencies = Object.assign(
|
||||
{},
|
||||
packageConfig.peerDependencies,
|
||||
packageConfig.dependencies
|
||||
);
|
||||
|
||||
const options = {
|
||||
// Ignore .babelrc and package.json babel config
|
||||
// because we haven't installed dependencies so
|
||||
// we can't load plugins; see #84
|
||||
babelrc: false,
|
||||
plugins: [unpkgRewrite(dependencies)]
|
||||
};
|
||||
|
||||
return babel.transform(code, options).code;
|
||||
}
|
||||
|
||||
function getContentTypeHeader(type) {
|
||||
return type === "application/javascript" ? type + "; charset=utf-8" : type;
|
||||
}
|
||||
|
||||
function serveJavaScriptModule(req, res) {
|
||||
if (req.entry.contentType !== "application/javascript") {
|
||||
return res
|
||||
.status(403)
|
||||
.type("text")
|
||||
.send("?module mode is available only for JavaScript files");
|
||||
}
|
||||
|
||||
try {
|
||||
const code = rewriteBareModuleIdentifiers(
|
||||
req.entry.content.toString("utf8"),
|
||||
req.packageConfig
|
||||
);
|
||||
|
||||
res
|
||||
.set({
|
||||
"Content-Length": Buffer.byteLength(code),
|
||||
"Content-Type": getContentTypeHeader(req.entry.contentType),
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
ETag: etag(code),
|
||||
"Cache-Tag": "file,js-file,js-module"
|
||||
})
|
||||
.send(code);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const errorName = error.constructor.name;
|
||||
const errorMessage = error.message.replace(
|
||||
/^.*?\/unpkg-.+?\//,
|
||||
`/${req.packageSpec}/`
|
||||
);
|
||||
const codeFrame = error.codeFrame;
|
||||
const debugInfo = `${errorName}: ${errorMessage}\n\n${codeFrame}`;
|
||||
|
||||
res
|
||||
.status(500)
|
||||
.type("text")
|
||||
.send(
|
||||
`Cannot generate module for ${req.packageSpec}${
|
||||
req.filename
|
||||
}\n\n${debugInfo}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function serveStaticFile(req, res) {
|
||||
const tags = ["file"];
|
||||
|
||||
const ext = path.extname(req.entry.name).substr(1);
|
||||
if (ext) {
|
||||
tags.push(`${ext}-file`);
|
||||
}
|
||||
|
||||
res
|
||||
.set({
|
||||
"Content-Length": req.entry.size,
|
||||
"Content-Type": getContentTypeHeader(req.entry.contentType),
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
"Last-Modified": req.entry.lastModified,
|
||||
ETag: etag(req.entry.content),
|
||||
"Cache-Tag": tags.join(",")
|
||||
})
|
||||
.send(req.entry.content);
|
||||
}
|
||||
|
||||
function serveIndex(req, res) {
|
||||
const html = renderPage(IndexPage, {
|
||||
packageInfo: req.packageInfo,
|
||||
version: req.packageVersion,
|
||||
filename: req.filename,
|
||||
entries: req.entries,
|
||||
entry: req.entry
|
||||
});
|
||||
|
||||
res
|
||||
.set({
|
||||
"Cache-Control": "public,max-age=60", // 1 minute
|
||||
"Cache-Tag": "index"
|
||||
})
|
||||
.send(html);
|
||||
}
|
||||
const serveAutoIndexPage = require("./serveAutoIndexPage");
|
||||
const serveJavaScriptModule = require("./serveJavaScriptModule");
|
||||
const serveStaticFile = require("./serveStaticFile");
|
||||
const serveMetadata = require("./serveMetadata");
|
||||
|
||||
/**
|
||||
* Send the file, JSON metadata, or HTML directory listing.
|
||||
|
@ -160,7 +12,7 @@ function serveFile(req, res) {
|
|||
}
|
||||
|
||||
if (req.entry.type === "directory") {
|
||||
return serveIndex(req, res);
|
||||
return serveAutoIndexPage(req, res);
|
||||
}
|
||||
|
||||
if (req.query.module != null) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
const etag = require("etag");
|
||||
const babel = require("babel-core");
|
||||
|
||||
const getContentTypeHeader = require("../utils/getContentTypeHeader");
|
||||
const unpkgRewrite = require("../plugins/unpkgRewrite");
|
||||
|
||||
function rewriteBareModuleIdentifiers(code, packageConfig) {
|
||||
const dependencies = Object.assign(
|
||||
{},
|
||||
packageConfig.peerDependencies,
|
||||
packageConfig.dependencies
|
||||
);
|
||||
|
||||
const options = {
|
||||
// Ignore .babelrc and package.json babel config
|
||||
// because we haven't installed dependencies so
|
||||
// we can't load plugins; see #84
|
||||
babelrc: false,
|
||||
plugins: [unpkgRewrite(dependencies)]
|
||||
};
|
||||
|
||||
return babel.transform(code, options).code;
|
||||
}
|
||||
|
||||
function serveJavaScriptModule(req, res) {
|
||||
if (req.entry.contentType !== "application/javascript") {
|
||||
return res
|
||||
.status(403)
|
||||
.type("text")
|
||||
.send("?module mode is available only for JavaScript files");
|
||||
}
|
||||
|
||||
try {
|
||||
const code = rewriteBareModuleIdentifiers(
|
||||
req.entry.content.toString("utf8"),
|
||||
req.packageConfig
|
||||
);
|
||||
|
||||
res
|
||||
.set({
|
||||
"Content-Length": Buffer.byteLength(code),
|
||||
"Content-Type": getContentTypeHeader(req.entry.contentType),
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
ETag: etag(code),
|
||||
"Cache-Tag": "file,js-file,js-module"
|
||||
})
|
||||
.send(code);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const errorName = error.constructor.name;
|
||||
const errorMessage = error.message.replace(
|
||||
/^.*?\/unpkg-.+?\//,
|
||||
`/${req.packageSpec}/`
|
||||
);
|
||||
const codeFrame = error.codeFrame;
|
||||
const debugInfo = `${errorName}: ${errorMessage}\n\n${codeFrame}`;
|
||||
|
||||
res
|
||||
.status(500)
|
||||
.type("text")
|
||||
.send(
|
||||
`Cannot generate module for ${req.packageSpec}${
|
||||
req.filename
|
||||
}\n\n${debugInfo}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = serveJavaScriptModule;
|
|
@ -1,13 +1,29 @@
|
|||
const MainPage = require("../components/MainPage");
|
||||
const MainPage = require("../client/MainPage");
|
||||
const renderPage = require("../utils/renderPage");
|
||||
|
||||
const globalScripts =
|
||||
process.env.NODE_ENV === "production"
|
||||
? [
|
||||
"/react@16.4.1/umd/react.production.min.js",
|
||||
"/react-dom@16.4.1/umd/react-dom.production.min.js",
|
||||
"/react-router-dom@4.3.1/umd/react-router-dom.min.js"
|
||||
]
|
||||
: [
|
||||
"/react@16.4.1/umd/react.development.js",
|
||||
"/react-dom@16.4.1/umd/react-dom.development.js",
|
||||
"/react-router-dom@4.3.1/umd/react-router-dom.js"
|
||||
];
|
||||
|
||||
function serveMainPage(req, res) {
|
||||
res.send(
|
||||
renderPage(MainPage, {
|
||||
scripts: req.assets.getScripts("main"),
|
||||
styles: req.assets.getStyles("main")
|
||||
})
|
||||
);
|
||||
const scripts = globalScripts.concat(req.assets.getScripts("main"));
|
||||
const styles = req.assets.getStyles("main");
|
||||
|
||||
const html = renderPage(MainPage, {
|
||||
scripts: scripts,
|
||||
styles: styles
|
||||
});
|
||||
|
||||
res.send(html);
|
||||
}
|
||||
|
||||
module.exports = serveMainPage;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
const path = require("path");
|
||||
|
||||
const addLeadingSlash = require("../utils/addLeadingSlash");
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || ".";
|
||||
|
||||
return Object.keys(entries)
|
||||
.filter(name => entry.name !== name && path.dirname(name) === dirname)
|
||||
.map(name => entries[name]);
|
||||
}
|
||||
|
||||
function getMetadata(entry, entries) {
|
||||
const metadata = {
|
||||
path: addLeadingSlash(entry.name),
|
||||
type: entry.type
|
||||
};
|
||||
|
||||
if (entry.type === "file") {
|
||||
metadata.contentType = entry.contentType;
|
||||
metadata.integrity = entry.integrity;
|
||||
metadata.lastModified = entry.lastModified;
|
||||
metadata.size = entry.size;
|
||||
} else if (entry.type === "directory") {
|
||||
metadata.files = getMatchingEntries(entry, entries).map(e =>
|
||||
getMetadata(e, entries)
|
||||
);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
function serveMetadata(req, res) {
|
||||
const metadata = getMetadata(req.entry, req.entries);
|
||||
|
||||
res
|
||||
.set({
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
"Cache-Tag": "meta"
|
||||
})
|
||||
.send(metadata);
|
||||
}
|
||||
|
||||
module.exports = serveMetadata;
|
|
@ -0,0 +1,26 @@
|
|||
const path = require("path");
|
||||
const etag = require("etag");
|
||||
|
||||
const getContentTypeHeader = require("../utils/getContentTypeHeader");
|
||||
|
||||
function serveStaticFile(req, res) {
|
||||
const tags = ["file"];
|
||||
|
||||
const ext = path.extname(req.entry.name).substr(1);
|
||||
if (ext) {
|
||||
tags.push(`${ext}-file`);
|
||||
}
|
||||
|
||||
res
|
||||
.set({
|
||||
"Content-Length": req.entry.size,
|
||||
"Content-Type": getContentTypeHeader(req.entry.contentType),
|
||||
"Cache-Control": "public,max-age=31536000", // 1 year
|
||||
"Last-Modified": req.entry.lastModified,
|
||||
ETag: etag(req.entry.content),
|
||||
"Cache-Tag": tags.join(",")
|
||||
})
|
||||
.send(req.entry.content);
|
||||
}
|
||||
|
||||
module.exports = serveStaticFile;
|
|
@ -0,0 +1,56 @@
|
|||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const h = require("./utils/createHTML");
|
||||
const x = require("./utils/execScript");
|
||||
|
||||
function MainPage({ title, description, scripts, styles, data, content }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,maximum-scale=1"
|
||||
/>
|
||||
<meta name="timestamp" content={new Date().toISOString()} />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
{styles.map(s => <link key={s} rel="stylesheet" href={s} />)}
|
||||
{x(
|
||||
"window.Promise || document.write('\\x3Cscript src=\"/_polyfills/es6-promise.min.js\">\\x3C/script>\\x3Cscript>ES6Promise.polyfill()\\x3C/script>')"
|
||||
)}
|
||||
{x(
|
||||
"window.fetch || document.write('\\x3Cscript src=\"/_polyfills/fetch.min.js\">\\x3C/script>')"
|
||||
)}
|
||||
{x(`window.__DATA__ = ${JSON.stringify(data)}`)}
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" dangerouslySetInnerHTML={h(content)} />
|
||||
{scripts.map(s => <script key={s} src={s} />)}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
MainPage.propTypes = {
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
scripts: PropTypes.arrayOf(PropTypes.string),
|
||||
styles: PropTypes.arrayOf(PropTypes.string),
|
||||
data: PropTypes.any,
|
||||
content: PropTypes.string
|
||||
};
|
||||
|
||||
MainPage.defaultProps = {
|
||||
title: "UNPKG",
|
||||
description: "The CDN for everything on npm",
|
||||
scripts: [],
|
||||
styles: [],
|
||||
data: {},
|
||||
content: ""
|
||||
};
|
||||
|
||||
module.exports = MainPage;
|
|
@ -0,0 +1,8 @@
|
|||
body {
|
||||
font-size: 14px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
line-height: 1.7;
|
||||
padding: 0px 10px 5px;
|
||||
color: #000000;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
require("./autoIndex.css");
|
||||
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
|
||||
const App = require("./autoIndex/App");
|
||||
|
||||
const props = window.__DATA__ || {};
|
||||
|
||||
ReactDOM.hydrate(<App {...props} />, document.getElementById("root"));
|
|
@ -0,0 +1,23 @@
|
|||
.app {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.app-version-selector {
|
||||
line-height: 2.25em;
|
||||
float: right;
|
||||
}
|
||||
.app-version-selector select {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.app-address {
|
||||
text-align: right;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
require("./App.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const DirectoryListing = require("./DirectoryListing");
|
||||
|
||||
class App extends React.Component {
|
||||
static defaultProps = {
|
||||
availableVersions: []
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
window.location.href = window.location.href.replace(
|
||||
"@" + this.props.packageVersion,
|
||||
"@" + event.target.value
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
<h1>
|
||||
Index of /{this.props.packageName}@{this.props.packageVersion}
|
||||
{this.props.filename}
|
||||
</h1>
|
||||
|
||||
<div className="app-version-selector">
|
||||
Version:{" "}
|
||||
<select
|
||||
id="version"
|
||||
defaultValue={this.props.packageVersion}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{this.props.availableVersions.map(v => (
|
||||
<option key={v} value={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<DirectoryListing
|
||||
filename={this.props.filename}
|
||||
entry={this.props.entry}
|
||||
entries={this.props.entries}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<address className="app-address">
|
||||
{this.props.packageName}@{this.props.packageVersion}
|
||||
</address>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const entryType = PropTypes.object;
|
||||
|
||||
App.propTypes = {
|
||||
packageName: PropTypes.string.isRequired,
|
||||
packageVersion: PropTypes.string.isRequired,
|
||||
availableVersions: PropTypes.arrayOf(PropTypes.string),
|
||||
filename: PropTypes.string.isRequired,
|
||||
entry: entryType.isRequired,
|
||||
entries: PropTypes.objectOf(entryType).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = App;
|
|
@ -0,0 +1,17 @@
|
|||
.directory-listing table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 0.85em Monaco, monospace;
|
||||
}
|
||||
|
||||
.directory-listing tr.even {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.directory-listing th {
|
||||
text-align: left;
|
||||
}
|
||||
.directory-listing th,
|
||||
.directory-listing td {
|
||||
padding: 0.5em 1em;
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
require("./DirectoryListing.css");
|
||||
|
||||
const React = require("react");
|
||||
const formatBytes = require("pretty-bytes");
|
||||
const sortBy = require("sort-by");
|
||||
|
||||
function getDirname(name) {
|
||||
return (
|
||||
name
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/") || "."
|
||||
);
|
||||
}
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || ".";
|
||||
|
||||
return Object.keys(entries)
|
||||
.filter(name => entry.name !== name && getDirname(name) === dirname)
|
||||
.map(name => entries[name]);
|
||||
}
|
||||
|
||||
function getRelativeName(base, name) {
|
||||
return base.length ? name.substr(base.length + 1) : name;
|
||||
}
|
||||
|
||||
function DirectoryListing({ filename, entry, entries }) {
|
||||
const rows = [];
|
||||
|
||||
if (filename !== "/") {
|
||||
rows.push(
|
||||
<tr key="..">
|
||||
<td>
|
||||
<a title="Parent directory" href="../">
|
||||
..
|
||||
</a>
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const matchingEntries = getMatchingEntries(entry, entries);
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "directory")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
const href = relName + "/";
|
||||
|
||||
rows.push(
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<a title={relName} href={href}>
|
||||
{href}
|
||||
</a>
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "file")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name, size, contentType, lastModified }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
|
||||
rows.push(
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<a title={relName} href={relName}>
|
||||
{relName}
|
||||
</a>
|
||||
</td>
|
||||
<td>{contentType}</td>
|
||||
<td>{formatBytes(size)}</td>
|
||||
<td>{lastModified}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="directory-listing">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
<th>Last Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, index) =>
|
||||
React.cloneElement(row, {
|
||||
className: index % 2 ? "odd" : "even"
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const entryType = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
DirectoryListing.propTypes = {
|
||||
filename: PropTypes.string.isRequired,
|
||||
entry: entryType.isRequired,
|
||||
entries: PropTypes.objectOf(entryType).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = DirectoryListing;
|
|
@ -0,0 +1,8 @@
|
|||
require("./main.css");
|
||||
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
|
||||
const App = require("./main/App");
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
|
@ -0,0 +1,12 @@
|
|||
require("./About.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const h = require("../utils/createHTML");
|
||||
const markup = require("./About.md");
|
||||
|
||||
function About() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={h(markup)} />;
|
||||
}
|
||||
|
||||
module.exports = About;
|
|
@ -0,0 +1,14 @@
|
|||
const React = require("react");
|
||||
const { HashRouter } = require("react-router-dom");
|
||||
|
||||
const Layout = require("./Layout");
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HashRouter>
|
||||
<Layout />
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = App;
|
Before ![]() (image error) Size: 9.1 KiB After ![]() (image error) Size: 9.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 13 KiB After ![]() (image error) Size: 13 KiB ![]() ![]() |
|
@ -0,0 +1,12 @@
|
|||
require("./Home.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const h = require("../utils/createHTML");
|
||||
const markup = require("./Home.md");
|
||||
|
||||
function Home() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={h(markup)} />;
|
||||
}
|
||||
|
||||
module.exports = Home;
|
|
@ -1,14 +1,14 @@
|
|||
import "./Layout.css";
|
||||
require("./Layout.css");
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Motion, spring } from "react-motion";
|
||||
import { Switch, Route, Link, withRouter } from "react-router-dom";
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
const { Switch, Route, Link, withRouter } = require("react-router-dom");
|
||||
const { Motion, spring } = require("react-motion");
|
||||
|
||||
import WindowSize from "./WindowSize";
|
||||
import About from "./About";
|
||||
import Stats from "./Stats";
|
||||
import Home from "./Home";
|
||||
const WindowSize = require("./WindowSize");
|
||||
const About = require("./About");
|
||||
const Stats = require("./Stats");
|
||||
const Home = require("./Home");
|
||||
|
||||
class Layout extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -132,4 +132,4 @@ class Layout extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default withRouter(Layout);
|
||||
module.exports = withRouter(Layout);
|
|
@ -1,14 +1,14 @@
|
|||
import "./Stats.css";
|
||||
require("./Stats.css");
|
||||
|
||||
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-list";
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
const formatBytes = require("pretty-bytes");
|
||||
const formatDate = require("date-fns/format");
|
||||
const parseDate = require("date-fns/parse");
|
||||
const { continents, countries } = require("countries-list");
|
||||
|
||||
import formatNumber from "../utils/formatNumber";
|
||||
import formatPercent from "../utils/formatPercent";
|
||||
const formatNumber = require("../utils/formatNumber");
|
||||
const formatPercent = require("../utils/formatPercent");
|
||||
|
||||
function getCountriesByContinent(continent) {
|
||||
return Object.keys(countries).filter(
|
||||
|
@ -320,4 +320,4 @@ class Stats extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Stats;
|
||||
module.exports = Stats;
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
import addEvent from "../utils/addEvent";
|
||||
import removeEvent from "../utils/removeEvent";
|
||||
const addEvent = require("../utils/addEvent");
|
||||
const removeEvent = require("../utils/removeEvent");
|
||||
|
||||
const resizeEvent = "resize";
|
||||
|
||||
|
@ -32,4 +32,4 @@ class WindowSize extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default WindowSize;
|
||||
module.exports = WindowSize;
|
|
@ -6,4 +6,4 @@ function addEvent(node, type, handler) {
|
|||
}
|
||||
}
|
||||
|
||||
export default addEvent;
|
||||
module.exports = addEvent;
|
|
@ -0,0 +1,5 @@
|
|||
function createHTML(code) {
|
||||
return { __html: code };
|
||||
}
|
||||
|
||||
module.exports = createHTML;
|
|
@ -0,0 +1,9 @@
|
|||
const React = require("react");
|
||||
|
||||
const h = require("./createHTML");
|
||||
|
||||
function execScript(code) {
|
||||
return <script dangerouslySetInnerHTML={h(code)} />;
|
||||
}
|
||||
|
||||
module.exports = execScript;
|
|
@ -0,0 +1,12 @@
|
|||
function formatNumber(n) {
|
||||
const digits = String(n).split("");
|
||||
const groups = [];
|
||||
|
||||
while (digits.length) {
|
||||
groups.unshift(digits.splice(-3).join(""));
|
||||
}
|
||||
|
||||
return groups.join(",");
|
||||
}
|
||||
|
||||
module.exports = formatNumber;
|
|
@ -0,0 +1,5 @@
|
|||
function formatPercent(n, fixed = 1) {
|
||||
return String((n.toPrecision(2) * 100).toFixed(fixed));
|
||||
}
|
||||
|
||||
module.exports = formatPercent;
|
|
@ -0,0 +1,5 @@
|
|||
function parseNumber(s) {
|
||||
return parseInt(s.replace(/,/g, ""), 10) || 0;
|
||||
}
|
||||
|
||||
module.exports = parseNumber;
|
|
@ -6,4 +6,4 @@ function removeEvent(node, type, handler) {
|
|||
}
|
||||
}
|
||||
|
||||
export default removeEvent;
|
||||
module.exports = removeEvent;
|
|
@ -0,0 +1,9 @@
|
|||
// Use babel to compile JSX on the fly.
|
||||
require("babel-register")({
|
||||
only: /server\/client/
|
||||
});
|
||||
|
||||
// Ignore require("*.css") calls.
|
||||
require.extensions[".css"] = function() {
|
||||
return {};
|
||||
};
|
|
@ -1,102 +0,0 @@
|
|||
const path = require("path");
|
||||
const formatBytes = require("pretty-bytes");
|
||||
const sortBy = require("sort-by");
|
||||
|
||||
const cloneElement = require("./utils/cloneElement");
|
||||
const e = require("./utils/createElement");
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || ".";
|
||||
|
||||
return Object.keys(entries)
|
||||
.filter(name => entry.name !== name && path.dirname(name) === dirname)
|
||||
.map(name => entries[name]);
|
||||
}
|
||||
|
||||
function getRelativeName(base, name) {
|
||||
return base.length ? name.substr(base.length + 1) : name;
|
||||
}
|
||||
|
||||
function DirectoryListing({ filename, entry, entries }) {
|
||||
const rows = [];
|
||||
|
||||
if (filename !== "/") {
|
||||
rows.push(
|
||||
e(
|
||||
"tr",
|
||||
{ key: ".." },
|
||||
e("td", null, e("a", { title: "Parent directory", href: "../" }, "..")),
|
||||
e("td", null, "-"),
|
||||
e("td", null, "-"),
|
||||
e("td", null, "-")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const matchingEntries = getMatchingEntries(entry, entries);
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "directory")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
const href = relName + "/";
|
||||
|
||||
rows.push(
|
||||
e(
|
||||
"tr",
|
||||
{ key: name },
|
||||
e("td", null, e("a", { title: relName, href }, href)),
|
||||
e("td", null, "-"),
|
||||
e("td", null, "-"),
|
||||
e("td", null, "-")
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "file")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name, size, contentType, lastModified }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
|
||||
rows.push(
|
||||
e(
|
||||
"tr",
|
||||
{ key: name },
|
||||
e("td", null, e("a", { title: relName, href: relName }, relName)),
|
||||
e("td", null, contentType),
|
||||
e("td", null, formatBytes(size)),
|
||||
e("td", null, lastModified)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return e(
|
||||
"table",
|
||||
null,
|
||||
e(
|
||||
"thead",
|
||||
null,
|
||||
e(
|
||||
"tr",
|
||||
null,
|
||||
e("th", null, "Name"),
|
||||
e("th", null, "Type"),
|
||||
e("th", null, "Size"),
|
||||
e("th", null, "Last Modified")
|
||||
)
|
||||
),
|
||||
e(
|
||||
"tbody",
|
||||
null,
|
||||
rows.map((row, index) =>
|
||||
cloneElement(row, {
|
||||
className: index % 2 ? "odd" : "even"
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = DirectoryListing;
|
|
@ -1,40 +0,0 @@
|
|||
body {
|
||||
font-size: 14px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
line-height: 1.7;
|
||||
padding: 0px 10px 5px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 0.85em Monaco, monospace;
|
||||
}
|
||||
tr.even {
|
||||
background-color: #eee;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.version-wrapper {
|
||||
line-height: 2.25em;
|
||||
float: right;
|
||||
}
|
||||
#version {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
address {
|
||||
text-align: right;
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
const semver = require("semver");
|
||||
|
||||
const DirectoryListing = require("./DirectoryListing");
|
||||
const readCSS = require("./utils/readCSS");
|
||||
const e = require("./utils/createElement");
|
||||
const s = require("./utils/createStyle");
|
||||
const x = require("./utils/createScript");
|
||||
|
||||
const IndexPageStyle = readCSS(__dirname, "IndexPage.css");
|
||||
const IndexPageScript = `
|
||||
var s = document.getElementById('version'), v = s.value;
|
||||
s.onchange = function () {
|
||||
window.location.href = window.location.href.replace('@' + v, '@' + s.value);
|
||||
};
|
||||
`;
|
||||
|
||||
function byVersion(a, b) {
|
||||
return semver.lt(a, b) ? -1 : semver.gt(a, b) ? 1 : 0;
|
||||
}
|
||||
|
||||
function IndexPage({ packageInfo, version, filename, entry, entries }) {
|
||||
const versions = Object.keys(packageInfo.versions).sort(byVersion);
|
||||
const options = versions.map(v =>
|
||||
e("option", { key: v, value: v }, `${packageInfo.name}@${v}`)
|
||||
);
|
||||
|
||||
return e(
|
||||
"html",
|
||||
null,
|
||||
e(
|
||||
"head",
|
||||
null,
|
||||
e("meta", { charSet: "utf-8" }),
|
||||
e("title", null, `Index of ${filename}`),
|
||||
s(IndexPageStyle)
|
||||
),
|
||||
e(
|
||||
"body",
|
||||
null,
|
||||
e(
|
||||
"div",
|
||||
{ className: "content-wrapper" },
|
||||
e(
|
||||
"div",
|
||||
{ className: "version-wrapper" },
|
||||
e("select", { id: "version", defaultValue: version }, options)
|
||||
),
|
||||
e("h1", null, `Index of ${filename}`),
|
||||
x(IndexPageScript),
|
||||
e("hr"),
|
||||
e(DirectoryListing, { filename, entry, entries }),
|
||||
e("hr"),
|
||||
e("address", null, `${packageInfo.name}@${version}`)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = IndexPage;
|
|
@ -1,61 +0,0 @@
|
|||
const PropTypes = require("prop-types");
|
||||
|
||||
const e = require("./utils/createElement");
|
||||
const x = require("./utils/createScript");
|
||||
|
||||
function MainPage({ title, description, scripts, styles, content }) {
|
||||
return e(
|
||||
"html",
|
||||
{ lang: "en" },
|
||||
e(
|
||||
"head",
|
||||
null,
|
||||
e("meta", { charSet: "utf-8" }),
|
||||
e("title", null, title),
|
||||
e("meta", { httpEquiv: "X-UA-Compatible", content: "IE=edge,chrome=1" }),
|
||||
e("meta", { name: "description", content: description }),
|
||||
e("meta", {
|
||||
name: "viewport",
|
||||
content: "width=device-width,initial-scale=1,maximum-scale=1"
|
||||
}),
|
||||
e("meta", { name: "timestamp", content: new Date().toISOString() }),
|
||||
e("link", { rel: "shortcut icon", href: "/favicon.ico" }),
|
||||
x(
|
||||
"window.Promise || document.write('\\x3Cscript src=\"/_polyfills/es6-promise.min.js\">\\x3C/script>\\x3Cscript>ES6Promise.polyfill()\\x3C/script>')"
|
||||
),
|
||||
x(
|
||||
"window.fetch || document.write('\\x3Cscript src=\"/_polyfills/fetch.min.js\">\\x3C/script>')"
|
||||
),
|
||||
e("script", { src: "/react@16.4.1/umd/react.production.min.js" }),
|
||||
e("script", { src: "/react-dom@16.4.1/umd/react-dom.production.min.js" }),
|
||||
e("script", {
|
||||
src: "/react-router-dom@4.3.1/umd/react-router-dom.min.js"
|
||||
}),
|
||||
styles.map(s => e("link", { key: s, rel: "stylesheet", href: s }))
|
||||
),
|
||||
e(
|
||||
"body",
|
||||
null,
|
||||
e("div", { id: "root", dangerouslySetInnerHTML: { __html: content } }),
|
||||
scripts.map(s => e("script", { key: s, src: s }))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
MainPage.propTypes = {
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
scripts: PropTypes.arrayOf(PropTypes.string),
|
||||
styles: PropTypes.arrayOf(PropTypes.string),
|
||||
content: PropTypes.string
|
||||
};
|
||||
|
||||
MainPage.defaultProps = {
|
||||
title: "UNPKG",
|
||||
description: "The CDN for everything on npm",
|
||||
scripts: [],
|
||||
styles: [],
|
||||
content: ""
|
||||
};
|
||||
|
||||
module.exports = MainPage;
|
|
@ -1,2 +0,0 @@
|
|||
const React = require("react");
|
||||
module.exports = React.cloneElement;
|
|
@ -1,2 +0,0 @@
|
|||
const React = require("react");
|
||||
module.exports = React.createElement;
|
|
@ -1,7 +0,0 @@
|
|||
const createElement = require("./createElement");
|
||||
|
||||
function createScript(code) {
|
||||
return createElement("script", { dangerouslySetInnerHTML: { __html: code } });
|
||||
}
|
||||
|
||||
module.exports = createScript;
|
|
@ -1,7 +0,0 @@
|
|||
const createElement = require("./createElement");
|
||||
|
||||
function createStyle(code) {
|
||||
return createElement("style", { dangerouslySetInnerHTML: { __html: code } });
|
||||
}
|
||||
|
||||
module.exports = createStyle;
|
|
@ -1,9 +0,0 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const csso = require("csso");
|
||||
|
||||
function readCSS(...args) {
|
||||
return csso.minify(fs.readFileSync(path.resolve(...args), "utf8")).css;
|
||||
}
|
||||
|
||||
module.exports = readCSS;
|
|
@ -0,0 +1,5 @@
|
|||
function getContentTypeHeader(type) {
|
||||
return type === "application/javascript" ? type + "; charset=utf-8" : type;
|
||||
}
|
||||
|
||||
module.exports = getContentTypeHeader;
|
|
@ -4,11 +4,8 @@ const ReactDOMServer = require("react-dom/server");
|
|||
const doctype = "<!DOCTYPE html>";
|
||||
|
||||
function renderPage(page, props) {
|
||||
const html = ReactDOMServer.renderToStaticMarkup(
|
||||
React.createElement(page, props)
|
||||
);
|
||||
|
||||
return doctype + html;
|
||||
const element = React.createElement(page, props);
|
||||
return doctype + ReactDOMServer.renderToStaticMarkup(element);
|
||||
}
|
||||
|
||||
module.exports = renderPage;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
main: path.resolve(__dirname, "client/main.js")
|
||||
main: path.resolve(__dirname, "./server/client/main.js"),
|
||||
autoIndex: path.resolve(__dirname, "./server/client/autoIndex.js")
|
||||
},
|
||||
|
||||
externals: {
|
||||
|
@ -21,7 +23,13 @@ module.exports = {
|
|||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, use: "babel-loader" },
|
||||
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: "style-loader",
|
||||
use: "css-loader"
|
||||
})
|
||||
},
|
||||
{ test: /\.md$/, use: ["html-loader", "markdown-loader"] },
|
||||
{ test: /\.png$/, use: "file-loader" }
|
||||
]
|
||||
|
@ -32,7 +40,8 @@ module.exports = {
|
|||
"process.env.NODE_ENV": JSON.stringify(
|
||||
process.env.NODE_ENV || "development"
|
||||
)
|
||||
})
|
||||
}),
|
||||
new ExtractTextPlugin("[name]-[hash:8].css")
|
||||
],
|
||||
|
||||
devtool:
|
||||
|
|
110
yarn.lock
110
yarn.lock
|
@ -132,7 +132,7 @@ ajv@^4.9.1:
|
|||
co "^4.6.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
|
||||
ajv@^5.0.0, ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||
dependencies:
|
||||
|
@ -363,6 +363,12 @@ async@^2.1.2, async@^2.1.4:
|
|||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@^2.4.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
|
||||
dependencies:
|
||||
lodash "^4.17.10"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
|
@ -426,6 +432,30 @@ babel-core@^6.0.0, babel-core@^6.26.0:
|
|||
slash "^1.0.0"
|
||||
source-map "^0.5.6"
|
||||
|
||||
babel-core@^6.26.3:
|
||||
version "6.26.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
|
||||
dependencies:
|
||||
babel-code-frame "^6.26.0"
|
||||
babel-generator "^6.26.0"
|
||||
babel-helpers "^6.24.1"
|
||||
babel-messages "^6.23.0"
|
||||
babel-register "^6.26.0"
|
||||
babel-runtime "^6.26.0"
|
||||
babel-template "^6.26.0"
|
||||
babel-traverse "^6.26.0"
|
||||
babel-types "^6.26.0"
|
||||
babylon "^6.18.0"
|
||||
convert-source-map "^1.5.1"
|
||||
debug "^2.6.9"
|
||||
json5 "^0.5.1"
|
||||
lodash "^4.17.4"
|
||||
minimatch "^3.0.4"
|
||||
path-is-absolute "^1.0.1"
|
||||
private "^0.1.8"
|
||||
slash "^1.0.0"
|
||||
source-map "^0.5.7"
|
||||
|
||||
babel-eslint@^8.0.3:
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.1.tgz#136888f3c109edc65376c23ebf494f36a3e03951"
|
||||
|
@ -780,8 +810,8 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015
|
|||
babel-template "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
|
||||
version "6.26.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
|
||||
dependencies:
|
||||
babel-plugin-transform-strict-mode "^6.24.1"
|
||||
babel-runtime "^6.26.0"
|
||||
|
@ -913,9 +943,9 @@ babel-plugin-transform-react-jsx@^6.24.1:
|
|||
babel-plugin-syntax-jsx "^6.8.0"
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-react-remove-prop-types@^0.4.13:
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.13.tgz#331cfc05099a808238311d78319c27460d481189"
|
||||
babel-plugin-transform-react-remove-prop-types@^0.4.14:
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.14.tgz#d3bf0ab39bd491c6670e71abd8642d4e6caae919"
|
||||
|
||||
babel-plugin-transform-regenerator@^6.22.0:
|
||||
version "6.26.0"
|
||||
|
@ -930,9 +960,9 @@ babel-plugin-transform-strict-mode@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-preset-env@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48"
|
||||
babel-preset-env@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
|
||||
dependencies:
|
||||
babel-plugin-check-es2015-constants "^6.22.0"
|
||||
babel-plugin-syntax-trailing-function-commas "^6.22.0"
|
||||
|
@ -961,7 +991,7 @@ babel-preset-env@^1.6.1:
|
|||
babel-plugin-transform-es2015-unicode-regex "^6.22.0"
|
||||
babel-plugin-transform-exponentiation-operator "^6.22.0"
|
||||
babel-plugin-transform-regenerator "^6.22.0"
|
||||
browserslist "^2.1.2"
|
||||
browserslist "^3.2.6"
|
||||
invariant "^2.2.2"
|
||||
semver "^5.3.0"
|
||||
|
||||
|
@ -1317,12 +1347,12 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
|
|||
caniuse-db "^1.0.30000639"
|
||||
electron-to-chromium "^1.2.7"
|
||||
|
||||
browserslist@^2.1.2:
|
||||
version "2.11.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2"
|
||||
browserslist@^3.2.6:
|
||||
version "3.2.8"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30000792"
|
||||
electron-to-chromium "^1.3.30"
|
||||
caniuse-lite "^1.0.30000844"
|
||||
electron-to-chromium "^1.3.47"
|
||||
|
||||
bser@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -1456,9 +1486,9 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
|||
version "1.0.30000808"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000808.tgz#30dfd83009d5704f02dffb37725068ed12a366bb"
|
||||
|
||||
caniuse-lite@^1.0.30000792:
|
||||
version "1.0.30000808"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz#7d759b5518529ea08b6705a19e70dbf401628ffc"
|
||||
caniuse-lite@^1.0.30000844:
|
||||
version "1.0.30000865"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz#70026616e8afe6e1442f8bb4e1092987d81a2f25"
|
||||
|
||||
capture-exit@^1.2.0:
|
||||
version "1.2.0"
|
||||
|
@ -1799,7 +1829,7 @@ content-type@~1.0.4:
|
|||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
|
||||
convert-source-map@^1.4.0, convert-source-map@^1.5.0:
|
||||
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
|
||||
|
||||
|
@ -1956,13 +1986,6 @@ css-selector-tokenizer@^0.7.0:
|
|||
fastparse "^1.1.1"
|
||||
regexpu-core "^1.0.0"
|
||||
|
||||
css-tree@1.0.0-alpha.27:
|
||||
version "1.0.0-alpha.27"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.27.tgz#f211526909c7dc940843d83b9376ed98ddb8de47"
|
||||
dependencies:
|
||||
mdn-data "^1.0.0"
|
||||
source-map "^0.5.3"
|
||||
|
||||
cssesc@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
|
||||
|
@ -2004,12 +2027,6 @@ cssesc@^0.1.0:
|
|||
postcss-value-parser "^3.2.3"
|
||||
postcss-zindex "^2.0.1"
|
||||
|
||||
csso@^3.1.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.0.tgz#acdbba5719e2c87bc801eadc032764b2e4b9d4e7"
|
||||
dependencies:
|
||||
css-tree "1.0.0-alpha.27"
|
||||
|
||||
csso@~2.3.1:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
|
||||
|
@ -2297,10 +2314,14 @@ ee-first@1.1.1:
|
|||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
|
||||
electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30:
|
||||
electron-to-chromium@^1.2.7:
|
||||
version "1.3.33"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz#bf00703d62a7c65238136578c352d6c5c042a545"
|
||||
|
||||
electron-to-chromium@^1.3.47:
|
||||
version "1.3.52"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz#d2d9f1270ba4a3b967b831c40ef71fb4d9ab5ce0"
|
||||
|
||||
elliptic@^6.0.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
|
||||
|
@ -2787,6 +2808,15 @@ extglob@^2.0.2, extglob@^2.0.4:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
extract-text-webpack-plugin@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
|
||||
dependencies:
|
||||
async "^2.4.1"
|
||||
loader-utils "^1.1.0"
|
||||
schema-utils "^0.3.0"
|
||||
webpack-sources "^1.0.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
@ -4558,7 +4588,7 @@ lodash.uniq@^4.5.0:
|
|||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
lodash@^4.13.1:
|
||||
lodash@^4.13.1, lodash@^4.17.10:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||
|
||||
|
@ -4666,10 +4696,6 @@ md5@^2.2.1:
|
|||
crypt "~0.0.1"
|
||||
is-buffer "~1.1.1"
|
||||
|
||||
mdn-data@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.0.tgz#a7056319da95a2d0881267d7263075042eb061e2"
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
|
@ -5780,7 +5806,7 @@ pretty-format@^22.4.0, pretty-format@^22.4.3:
|
|||
ansi-regex "^3.0.0"
|
||||
ansi-styles "^3.2.0"
|
||||
|
||||
private@^0.1.6, private@^0.1.7, private@~0.1.5:
|
||||
private@^0.1.6, private@^0.1.7, private@^0.1.8, private@~0.1.5:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||
|
||||
|
@ -6443,6 +6469,12 @@ sax@^1.2.4, sax@~1.2.1:
|
|||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
||||
schema-utils@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
|
||||
dependencies:
|
||||
ajv "^5.0.0"
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||
|
|
Loading…
Reference in New Issue