Move middleware utils into server/utils

This commit is contained in:
Michael Jackson
2018-05-21 13:26:00 -07:00
parent 269b756aeb
commit c792515d01
14 changed files with 59 additions and 40 deletions

View File

@ -1,5 +1,6 @@
const invariant = require("invariant");
const createBundle = require("./utils/createBundle");
const createBundle = require("../utils/createBundle");
/**
* An express middleware that sets req.bundle from the

View File

@ -1,8 +1,8 @@
const semver = require("semver");
const createPackageURL = require("../utils/createPackageURL");
const getPackageInfo = require("./utils/getPackageInfo");
const getPackage = require("./utils/getPackage");
const getPackageInfo = require("../utils/getPackageInfo");
const getPackage = require("../utils/getPackage");
function tagRedirect(req, res) {
// Cache tag redirects for 1 minute.

View File

@ -2,8 +2,8 @@ const fs = require("fs");
const path = require("path");
const createPackageURL = require("../utils/createPackageURL");
const createSearch = require("./utils/createSearch");
const incrementCounter = require("./utils/incrementCounter");
const createSearch = require("../utils/createSearch");
const incrementCounter = require("../utils/incrementCounter");
/**
* File extensions to look for when automatically resolving.

View File

@ -1,4 +1,4 @@
const createSearch = require("./utils/createSearch");
const createSearch = require("../utils/createSearch");
/**
* Redirect old URLs that we no longer support.

View File

@ -1,6 +1,7 @@
const fs = require("fs");
const invariant = require("invariant");
const createBundle = require("./utils/createBundle");
const createBundle = require("../utils/createBundle");
/**
* An express middleware that sets req.bundle from the build
@ -14,7 +15,7 @@ function staticAssets(webpackStatsFile) {
invariant(
false,
"staticAssets middleware cannot read the build stats in %s; " +
"run `yarn build` before starting the server",
"run the `build` script before starting the server",
webpackStatsFile
);
}

View File

@ -1,28 +0,0 @@
/**
* Creates a bundle object that is stored on req.bundle.
*/
function createBundle(webpackStats) {
const { publicPath, assetsByChunkName } = webpackStats;
const createURL = asset => publicPath + asset;
const getAssets = (chunks = ["main"]) =>
(Array.isArray(chunks) ? chunks : [chunks])
.reduce((memo, chunk) => memo.concat(assetsByChunkName[chunk] || []), [])
.map(createURL);
const getScripts = (...args) =>
getAssets(...args).filter(asset => /\.js$/.test(asset));
const getStyles = (...args) =>
getAssets(...args).filter(asset => /\.css$/.test(asset));
return {
createURL,
getAssets,
getScripts,
getStyles
};
}
module.exports = createBundle;

View File

@ -1,29 +0,0 @@
const db = require("../../utils/redis");
function createCache(keyPrefix) {
function createKey(key) {
return keyPrefix + "-" + key;
}
function set(key, value, expiry, callback) {
db.setex(createKey(key), expiry, JSON.stringify(value), callback);
}
function get(key, callback) {
db.get(createKey(key), function(error, value) {
callback(error, value && JSON.parse(value));
});
}
function del(key, callback) {
db.del(createKey(key), callback);
}
return {
set,
get,
del
};
}
module.exports = createCache;

View File

@ -1,21 +0,0 @@
function createMutex(doWork) {
const mutex = Object.create(null);
return (key, payload, callback) => {
if (mutex[key]) {
mutex[key].push(callback);
} else {
mutex[key] = [callback];
doWork(payload, (error, value) => {
mutex[key].forEach(callback => {
callback(error, value);
});
delete mutex[key];
});
}
};
}
module.exports = createMutex;

View File

@ -1,17 +0,0 @@
function createSearch(query) {
const params = [];
Object.keys(query).forEach(param => {
if (query[param] === "") {
params.push(param); // Omit the trailing "=" from param=
} else {
params.push(`${param}=${encodeURIComponent(query[param])}`);
}
});
const search = params.join("&");
return search ? `?${search}` : "";
}
module.exports = createSearch;

View File

@ -1,89 +0,0 @@
require("isomorphic-fetch");
const fs = require("fs");
const path = require("path");
const tmpdir = require("os-tmpdir");
const gunzip = require("gunzip-maybe");
const mkdirp = require("mkdirp");
const tar = require("tar-fs");
const createMutex = require("./createMutex");
function createTempPath(name, version) {
const normalName = name.replace(/\//g, "-");
return path.join(tmpdir(), `unpkg-${normalName}-${version}`);
}
function stripNamePrefix(headers) {
// Most packages have header names that look like "package/index.js"
// so we shorten that to just "index.js" here. A few packages use a
// prefix other than "package/". e.g. the firebase package uses the
// "firebase_npm/" prefix. So we just strip the first dir name.
headers.name = headers.name.replace(/^[^/]+\//, "");
return headers;
}
function ignoreLinks(file, headers) {
return headers.type === "link" || headers.type === "symlink";
}
function extractResponse(response, outputDir) {
return new Promise((resolve, reject) => {
const extract = tar.extract(outputDir, {
readable: true, // All dirs/files should be readable.
map: stripNamePrefix,
ignore: ignoreLinks
});
response.body
.pipe(gunzip())
.pipe(extract)
.on("finish", resolve)
.on("error", reject);
});
}
function fetchAndExtract(tarballURL, outputDir) {
console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`);
return fetch(tarballURL).then(response => {
return extractResponse(response, outputDir);
});
}
const fetchMutex = createMutex((payload, callback) => {
const { tarballURL, outputDir } = payload;
fs.access(outputDir, error => {
if (error) {
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
// ENOENT or ENOTDIR are to be expected when we haven't yet
// fetched a package for the first time. Carry on!
mkdirp(outputDir, error => {
if (error) {
callback(error);
} else {
fetchAndExtract(tarballURL, outputDir).then(() => {
callback();
}, callback);
}
});
} else {
callback(error);
}
} else {
// Best case: we already have this package cached on disk!
callback();
}
});
});
function getPackage(packageConfig, callback) {
const tarballURL = packageConfig.dist.tarball;
const outputDir = createTempPath(packageConfig.name, packageConfig.version);
fetchMutex(tarballURL, { tarballURL, outputDir }, error => {
callback(error, outputDir);
});
}
module.exports = getPackage;

View File

@ -1,72 +0,0 @@
require("isomorphic-fetch");
const config = require("../../config");
const createCache = require("./createCache");
const createMutex = require("./createMutex");
const packageInfoCache = createCache("packageInfo");
function fetchPackageInfo(packageName) {
console.log(`info: Fetching package info for ${packageName}`);
let encodedPackageName;
if (packageName.charAt(0) === "@") {
encodedPackageName = `@${encodeURIComponent(packageName.substring(1))}`;
} else {
encodedPackageName = encodeURIComponent(packageName);
}
const url = `${config.registryURL}/${encodedPackageName}`;
return fetch(url, {
headers: {
Accept: "application/json"
}
}).then(res => {
return res.status === 404 ? null : res.json();
});
}
const PackageNotFound = "PackageNotFound";
// This mutex prevents multiple concurrent requests to
// the registry for the same package info.
const fetchMutex = createMutex((packageName, callback) => {
fetchPackageInfo(packageName).then(
value => {
if (value == null) {
// Cache 404s for 5 minutes. This prevents us from making
// unnecessary requests to the registry for bad package names.
// In the worst case, a brand new package's info will be
// available within 5 minutes.
packageInfoCache.set(packageName, PackageNotFound, 300, () => {
callback(null, value);
});
} else {
// Cache valid package info for 1 minute.
packageInfoCache.set(packageName, value, 60, () => {
callback(null, value);
});
}
},
error => {
// Do not cache errors.
packageInfoCache.del(packageName, () => {
callback(error);
});
}
);
});
function getPackageInfo(packageName, callback) {
packageInfoCache.get(packageName, (error, value) => {
if (error || value != null) {
callback(error, value === PackageNotFound ? null : value);
} else {
fetchMutex(packageName, packageName, callback);
}
});
}
module.exports = getPackageInfo;

View File

@ -1,15 +0,0 @@
const db = require("../../utils/redis");
function incrementCounter(counter, key, by) {
return new Promise((resolve, reject) => {
db.hincrby(counter, key, by, (error, value) => {
if (error) {
reject(error);
} else {
resolve(value);
}
});
});
}
module.exports = incrementCounter;

View File

@ -1,4 +1,4 @@
const createSearch = require("./utils/createSearch");
const createSearch = require("../utils/createSearch");
const knownQueryParams = {
main: true, // Deprecated, see #63