Better dev server integration
This commit is contained in:
35
server/utils/__tests__/getFileContentType-test.js
Normal file
35
server/utils/__tests__/getFileContentType-test.js
Normal file
@ -0,0 +1,35 @@
|
||||
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")
|
||||
})
|
76
server/utils/__tests__/unpkgRewriteBabelPlugin-test.js
Normal file
76
server/utils/__tests__/unpkgRewriteBabelPlugin-test.js
Normal file
@ -0,0 +1,76 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
25
server/utils/getEntries.js
Normal file
25
server/utils/getEntries.js
Normal file
@ -0,0 +1,25 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const getFileStats = require("./getFileStats");
|
||||
|
||||
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 };
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = getEntries;
|
13
server/utils/getFileContentType.js
Normal file
13
server/utils/getFileContentType.js
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
15
server/utils/getFileStats.js
Normal file
15
server/utils/getFileStats.js
Normal file
@ -0,0 +1,15 @@
|
||||
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
|
12
server/utils/getFileType.js
Normal file
12
server/utils/getFileType.js
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
74
server/utils/getMetadata.js
Normal file
74
server/utils/getMetadata.js
Normal file
@ -0,0 +1,74 @@
|
||||
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
|
9
server/utils/readCSS.js
Normal file
9
server/utils/readCSS.js
Normal file
@ -0,0 +1,9 @@
|
||||
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
|
11
server/utils/renderPage.js
Normal file
11
server/utils/renderPage.js
Normal file
@ -0,0 +1,11 @@
|
||||
const React = require("react")
|
||||
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
|
||||
}
|
||||
|
||||
module.exports = renderPage
|
44
server/utils/unpkgRewriteBabelPlugin.js
Normal file
44
server/utils/unpkgRewriteBabelPlugin.js
Normal file
@ -0,0 +1,44 @@
|
||||
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
|
Reference in New Issue
Block a user