Better dev server integration

This commit is contained in:
MICHAEL JACKSON
2018-02-16 16:00:06 -08:00
parent a22e0fa801
commit d6f2bc089a
42 changed files with 1753 additions and 1154 deletions

View File

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

View File

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

View File

@ -1,35 +0,0 @@
const getFileContentType = require("../getFileContentType")
it("gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile", () => {
expect(getFileContentType("AUTHORS")).toBe("text/plain")
expect(getFileContentType("CHANGES")).toBe("text/plain")
expect(getFileContentType("LICENSE")).toBe("text/plain")
expect(getFileContentType("Makefile")).toBe("text/plain")
expect(getFileContentType("PATENTS")).toBe("text/plain")
expect(getFileContentType("README")).toBe("text/plain")
})
it("gets a content type of text/plain for .*rc files", () => {
expect(getFileContentType(".eslintrc")).toBe("text/plain")
expect(getFileContentType(".babelrc")).toBe("text/plain")
expect(getFileContentType(".anythingrc")).toBe("text/plain")
})
it("gets a content type of text/plain for .git* files", () => {
expect(getFileContentType(".gitignore")).toBe("text/plain")
expect(getFileContentType(".gitanything")).toBe("text/plain")
})
it("gets a content type of text/plain for .*ignore files", () => {
expect(getFileContentType(".eslintignore")).toBe("text/plain")
expect(getFileContentType(".anythingignore")).toBe("text/plain")
})
it("gets a content type of text/plain for .ts files", () => {
expect(getFileContentType("app.ts")).toBe("text/plain")
expect(getFileContentType("app.d.ts")).toBe("text/plain")
})
it("gets a content type of text/plain for .flow files", () => {
expect(getFileContentType("app.js.flow")).toBe("text/plain")
})

View File

@ -1,76 +0,0 @@
const babel = require("babel-core")
const unpkgRewrite = require("../unpkgRewriteBabelPlugin")
const testCases = [
{
before: "import React from 'react';",
after: "import React from 'https://unpkg.com/react@15.6.1?module';"
},
{
before: "import router from '@angular/router';",
after: "import router from 'https://unpkg.com/@angular/router@4.3.5?module';"
},
{
before: "import map from 'lodash.map';",
after: "import map from 'https://unpkg.com/lodash.map@4.6.0?module';"
},
{
before: "import fs from 'pn/fs';",
after: "import fs from 'https://unpkg.com/pn@1.0.0/fs?module';"
},
{
before: "import cupcakes from './cupcakes';",
after: "import cupcakes from './cupcakes?module';"
},
{
before: "import shoelaces from '/shoelaces';",
after: "import shoelaces from '/shoelaces?module';"
},
{
before: "import something from '//something.com/whatevs';",
after: "import something from '//something.com/whatevs';"
},
{
before: "import something from 'http://something.com/whatevs';",
after: "import something from 'http://something.com/whatevs';"
},
{
before: "let ReactDOM = require('react-dom');",
after: "let ReactDOM = require('react-dom');"
},
{
before: "export React from 'react';",
after: "export React from 'https://unpkg.com/react@15.6.1?module';"
},
{
before: "export { Component } from 'react';",
after: "export { Component } from 'https://unpkg.com/react@15.6.1?module';"
},
{
before: "export * from 'react';",
after: "export * from 'https://unpkg.com/react@15.6.1?module';"
},
{
before: "export var message = 'hello';",
after: "export var message = 'hello';"
}
]
const dependencies = {
react: "15.6.1",
"@angular/router": "4.3.5",
"lodash.map": "4.6.0",
pn: "1.0.0"
}
describe("Rewriting imports/exports", () => {
testCases.forEach(testCase => {
it(`successfully rewrites "${testCase.before}"`, () => {
const result = babel.transform(testCase.before, {
plugins: [unpkgRewrite(dependencies)]
})
expect(result.code).toEqual(testCase.after)
})
})
})

View File

@ -0,0 +1,28 @@
/**
* 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,13 +0,0 @@
const mime = require("mime")
mime.define({
"text/plain": ["authors", "changes", "license", "makefile", "patents", "readme", "ts", "flow"]
})
const TextFiles = /\/?(\.[a-z]*rc|\.git[a-z]*|\.[a-z]*ignore)$/i
function getFileContentType(file) {
return TextFiles.test(file) ? "text/plain" : mime.lookup(file)
}
module.exports = getFileContentType

View File

@ -1,15 +0,0 @@
const fs = require("fs")
function getFileStats(file) {
return new Promise((resolve, reject) => {
fs.lstat(file, (error, stats) => {
if (error) {
reject(error)
} else {
resolve(stats)
}
})
})
}
module.exports = getFileStats

View File

@ -1,12 +0,0 @@
function getFileType(stats) {
if (stats.isFile()) return "file"
if (stats.isDirectory()) return "directory"
if (stats.isBlockDevice()) return "blockDevice"
if (stats.isCharacterDevice()) return "characterDevice"
if (stats.isSymbolicLink()) return "symlink"
if (stats.isSocket()) return "socket"
if (stats.isFIFO()) return "fifo"
return "unknown"
}
module.exports = getFileType

View File

@ -1,40 +0,0 @@
const fs = require("fs")
const path = require("path")
const React = require("react")
const ReactDOMServer = require("react-dom/server")
const getFileStats = require("./getFileStats")
const IndexPage = require("../components/IndexPage")
const e = React.createElement
function getEntries(dir) {
return new Promise((resolve, reject) => {
fs.readdir(dir, function(error, files) {
if (error) {
reject(error)
} else {
resolve(
Promise.all(files.map(file => getFileStats(path.join(dir, file)))).then(statsArray => {
return statsArray.map((stats, index) => {
return { file: files[index], stats }
})
})
)
}
})
})
}
const DOCTYPE = "<!DOCTYPE html>"
function createHTML(props) {
return DOCTYPE + ReactDOMServer.renderToStaticMarkup(e(IndexPage, props))
}
function getIndexHTML(packageInfo, version, baseDir, dir) {
return getEntries(path.join(baseDir, dir)).then(entries =>
createHTML({ packageInfo, version, dir, entries })
)
}
module.exports = getIndexHTML

View File

@ -1,74 +0,0 @@
const fs = require("fs")
const path = require("path")
const SRIToolbox = require("sri-toolbox")
const getFileContentType = require("./getFileContentType")
const getFileStats = require("./getFileStats")
const getFileType = require("./getFileType")
function getEntries(dir, file, maximumDepth) {
return new Promise((resolve, reject) => {
fs.readdir(path.join(dir, file), function(error, files) {
if (error) {
reject(error)
} else {
resolve(
Promise.all(files.map(f => getFileStats(path.join(dir, file, f)))).then(statsArray => {
return Promise.all(
statsArray.map((stats, index) =>
getMetadataRecursive(dir, path.join(file, files[index]), stats, maximumDepth - 1)
)
)
})
)
}
})
})
}
function formatTime(time) {
return new Date(time).toISOString()
}
function getIntegrity(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, function(error, data) {
if (error) {
reject(error)
} else {
resolve(SRIToolbox.generate({ algorithms: ["sha384"] }, data))
}
})
})
}
function getMetadataRecursive(dir, file, stats, maximumDepth) {
const metadata = {
lastModified: formatTime(stats.mtime),
contentType: getFileContentType(file),
path: file,
size: stats.size,
type: getFileType(stats)
}
if (stats.isFile()) {
return getIntegrity(path.join(dir, file)).then(integrity => {
metadata.integrity = integrity
return metadata
})
}
if (!stats.isDirectory() || maximumDepth === 0) return Promise.resolve(metadata)
return getEntries(dir, file, maximumDepth).then(files => {
metadata.files = files
return metadata
})
}
function getMetadata(baseDir, path, stats, maximumDepth, callback) {
getMetadataRecursive(baseDir, path, stats, maximumDepth).then(function(metadata) {
callback(null, metadata)
}, callback)
}
module.exports = getMetadata

View File

@ -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

View File

@ -1,44 +0,0 @@
const URL = require("whatwg-url")
const warning = require("warning")
const BareIdentifierFormat = /^((?:@[^\/]+\/)?[^\/]+)(\/.*)?$/
function unpkgRewriteBabelPlugin(dependencies = {}) {
return {
inherits: require("babel-plugin-syntax-export-extensions"),
visitor: {
"ImportDeclaration|ExportNamedDeclaration|ExportAllDeclaration"(path) {
if (!path.node.source) return // probably a variable declaration
if (
URL.parseURL(path.node.source.value) != null ||
path.node.source.value.substr(0, 2) === "//"
)
return // valid URL or URL w/o protocol, leave it alone
if ([".", "/"].indexOf(path.node.source.value.charAt(0)) >= 0) {
// local path
path.node.source.value = `${path.node.source.value}?module`
} else {
// "bare" identifier
const match = BareIdentifierFormat.exec(path.node.source.value)
const packageName = match[1]
const file = match[2] || ""
warning(
dependencies[packageName],
'Missing version info for package "%s" in dependencies; falling back to "latest"',
packageName
)
const version = dependencies[packageName] || "latest"
path.node.source.value = `https://unpkg.com/${packageName}@${version}${file}?module`
}
}
}
}
}
module.exports = unpkgRewriteBabelPlugin