Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions bin/faucet
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env node
"use strict";
import { faucetDispatch } from "../lib/index.js";
import { parseCLI } from "../lib/cli.js";

let faucet = require("../lib");
let parseCLI = require("../lib/cli");

let { referenceDir, config, options } = parseCLI();
faucet(referenceDir, config, options);
parseCLI().
then(({ referenceDir, config, options }) => {
faucetDispatch(referenceDir, config, options);
});
14 changes: 6 additions & 8 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use strict";

let readConfig = require("./config");
let { abort, repr } = require("./util");
let parseArgs = require("minimist");
import { readConfig } from "./config.js";
import { abort, repr } from "./util/index.js";
import parseArgs from "minimist";

let HELP = `
Usage:
Expand All @@ -27,7 +25,7 @@ Options:
serve generated files via HTTP with live reloading
`.trim();

module.exports = function parseCLI(argv = process.argv.slice(2), help = HELP) {
export async function parseCLI(argv = process.argv.slice(2), help = HELP) {
argv = parseArgs(argv, {
boolean: ["watch", "fingerprint", "sourcemaps", "compact"],
alias: {
Expand Down Expand Up @@ -56,6 +54,6 @@ module.exports = function parseCLI(argv = process.argv.slice(2), help = HELP) {
}

let rootDir = process.cwd();
let { referenceDir, config } = readConfig(rootDir, argv.config);
let { referenceDir, config } = await readConfig(rootDir, argv.config);
return { referenceDir, config, options };
};
}
10 changes: 4 additions & 6 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use strict";
import path from "path";

let path = require("path");

module.exports = function readConfig(rootDir, filepath = "faucet.config.js") {
export async function readConfig(rootDir, filepath = "faucet.config.js") {
let configPath = path.resolve(rootDir, filepath);
return {
referenceDir: path.dirname(configPath),
config: require(configPath)
config: await import(configPath)
};
};
}
36 changes: 18 additions & 18 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"use strict";
import { liveServer, staticServer } from "./server.js";
import { pluginsByBucket } from "./plugins.js";
import { AssetManager } from "./manager.js";
import { resolvePath } from "./util/resolve.js";
import { abort, repr } from "./util/index.js";
import { SerializedRunner } from "./util/runner.js";
import browserslist from "browserslist";

let server = require("./server");
let { pluginsByBucket } = require("./plugins");
let AssetManager = require("./manager");
let resolvePath = require("./util/resolve");
let { abort, repr } = require("./util");
let SerializedRunner = require("./util/runner");
let browserslist = require("browserslist");

module.exports = async function faucetDispatch(referenceDir, config,
export async function faucetDispatch(referenceDir, config,
{ watch, fingerprint, sourcemaps, compact, serve, liveserve }) {
config = await config;

Expand All @@ -22,7 +20,7 @@ module.exports = async function faucetDispatch(referenceDir, config,
browsers = [browsers];
}

let plugins = pluginsByBucket(config);
let plugins = await pluginsByBucket(config);
// initialize plugins with corresponding configuration
let buckets = Object.keys(plugins).reduce((memo, bucket) => {
memo[bucket] = plugins[bucket].map(({ plugin, config }) => {
Expand All @@ -40,28 +38,30 @@ module.exports = async function faucetDispatch(referenceDir, config,

if(watch) {
makeWatcher(config.watchDirs, referenceDir).
on("edit", filepaths => {
runner.rerun(filepaths);
then(watcher => {
watcher.on("edit", filepaths => {
runner.rerun(filepaths);
});
});
}

if(serve && liveserve) {
abort("ERROR: serve and liveserve must not be used together");
}
if(serve) {
server.static(serve, assetManager.manifest.webRoot);
staticServer(serve, assetManager.manifest.webRoot);
} else if(liveserve) {
server.live(liveserve, assetManager.manifest.webRoot);
liveServer(liveserve, assetManager.manifest.webRoot);
}
};
}

function buildStep(plugins) {
return files => Promise.all(plugins.map(plugin => plugin(files))).
then(() => files);
}

function makeWatcher(watchDirs, referenceDir) {
let niteOwl = require("nite-owl");
async function makeWatcher(watchDirs, referenceDir) {
let niteOwl = await import("nite-owl");

if(watchDirs) {
watchDirs = watchDirs.map(dir => resolvePath(dir, referenceDir,
Expand Down
16 changes: 7 additions & 9 deletions lib/manager.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"use strict";
import { Manifest } from "./manifest.js";
import { createFile } from "./util/files/index.js";
import { resolvePath } from "./util/resolve.js";
import { reportFileStatus, abort, generateFingerprint } from "./util/index.js";
import path from "path";

let Manifest = require("./manifest");
let createFile = require("./util/files");
let resolvePath = require("./util/resolve");
let { reportFileStatus, abort, generateFingerprint } = require("./util");
let path = require("path");

module.exports = class AssetManager {
export class AssetManager {
constructor(referenceDir, { manifestConfig, fingerprint, exitOnError } = {}) {
this.referenceDir = referenceDir;
this.fingerprint = fingerprint;
Expand Down Expand Up @@ -57,4 +55,4 @@ module.exports = class AssetManager {
actualPath = path.relative(referenceDir, actualPath);
return this.manifest.set(originalPath, actualPath, targetDir);
}
};
}
14 changes: 6 additions & 8 deletions lib/manifest.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use strict";
import { createFile } from "./util/files/index.js";
import { resolvePath } from "./util/resolve.js";
import { abort } from "./util/index.js";
import path from "path";

let createFile = require("./util/files");
let resolvePath = require("./util/resolve");
let { abort } = require("./util");
let path = require("path");

module.exports = class Manifest {
export class Manifest {
constructor(referenceDir, { target, key, value, baseURI, webRoot } = {}) {
if(value && (baseURI || webRoot)) {
abort("ERROR: `value` must not be used with `baseURI` and/or `webRoot`");
Expand Down Expand Up @@ -52,4 +50,4 @@ module.exports = class Manifest {
return memo;
}, {});
}
};
}
52 changes: 24 additions & 28 deletions lib/plugins.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"use strict";

let { loadExtension, abort, repr } = require("./util");
import { loadExtension, abort, repr } from "./util/index.js";

// common plugins included for convenience
let DEFAULTS = [{
Expand All @@ -26,54 +24,52 @@ let DEFAULT_KEYS = DEFAULTS.reduce((memo, plugin) => {
}, new Set());
let BUCKETS = new Set(["static", "scripts", "styles", "markup"]);

module.exports = {
pluginsByBucket,
_determinePlugins: determinePlugins
};

// returns plugin functions grouped by bucket and filtered by relevance (based
// on configuration)
function pluginsByBucket(config, defaults) {
let plugins = determinePlugins(config.plugins, defaults);
export async function pluginsByBucket(config, defaults) {
let plugins = await _determinePlugins(config.plugins, defaults);
let buckets = [...BUCKETS].reduce((memo, bucket) => {
memo[bucket] = [];
return memo;
}, {});
Object.entries(plugins).forEach(([key, _plugin]) => {
for(let [key, _plugin] of Object.entries(plugins)) {
let pluginConfig = config[key];
if(!pluginConfig) {
return;
continue;
}

let { bucket, plugin } = _plugin;
if(!plugin.call) {
({ plugin } = await loadPlugin(plugin));
}
buckets[bucket].push({
plugin: plugin.call ? plugin : loadPlugin(plugin).plugin,
plugin,
config: pluginConfig
});
});
}
return buckets;
}

// `plugins` is an array of plugins, each either a package identifier or a
// `{ key, bucket, plugin }` object`, with `key` being the configuration key and
// `plugin` being either a function or a package identifier
function determinePlugins(plugins = [], defaults = DEFAULTS) {
export async function _determinePlugins(plugins = [], defaults = DEFAULTS) {
let registry = {};
// NB: default plugins are resolved lazily because eager loading would
// result in them becoming a hard dependency rather than a convenience
// preset - however, that requires us to duplicate the respective
// configuration keys and buckets here
defaults.forEach(plugin => {
registerPlugin(registry, plugin, false);
});
plugins.forEach(plugin => {
registerPlugin(registry, plugin, true);
});
for(let plugin of defaults) {
await registerPlugin(registry, plugin, false);
}
for(let plugin of plugins) {
await registerPlugin(registry, plugin, true);
}
return registry;
}

function registerPlugin(registry, _plugin, eager) {
let { key, bucket, plugin } = resolvePlugin(_plugin, eager);
async function registerPlugin(registry, _plugin, eager) {
let { key, bucket, plugin } = await resolvePlugin(_plugin, eager);
// NB: default plugins may be overridden
if(registry[key] && !DEFAULT_KEYS.has(key)) {
abort(`ERROR: duplicate plugin key ${repr(key, false)}`);
Expand All @@ -84,14 +80,14 @@ function registerPlugin(registry, _plugin, eager) {
}
}

function resolvePlugin(_plugin, eager) {
async function resolvePlugin(_plugin, eager) {
if(_plugin.substr) { // package identifier
_plugin = loadPlugin(_plugin);
_plugin = await loadPlugin(_plugin);
}

let { key, bucket, plugin } = _plugin;
if(eager && plugin.substr && (!key || !bucket)) { // auto-configuration
let _plugin = loadPlugin(plugin);
let _plugin = await loadPlugin(plugin);
plugin = _plugin.plugin;
// local configuration takes precedence
key = key || _plugin.key;
Expand All @@ -104,13 +100,13 @@ function resolvePlugin(_plugin, eager) {
return { key, bucket, plugin };
}

function loadPlugin(pkg) {
async function loadPlugin(pkg) {
let fail = prop => abort(`ERROR: invalid plugin ${repr(pkg)}; ` +
`missing ${repr(prop, false)}`);
let {
key = fail("key"),
bucket = fail("bucket"),
plugin = fail("plugin")
} = loadExtension(pkg, "ERROR: missing plugin");
} = await loadExtension(pkg, "ERROR: missing plugin");
return { key, bucket, plugin };
}
32 changes: 13 additions & 19 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
let { loadExtension, abort, repr } = require("./util");
import { loadExtension, abort, repr } from "./util/index.js";

let DEFAULTS = {
host: "localhost",
port: 3000
};

exports.static = (config, webroot) => {
let donny = loadExtension("donny", "failed to activate server");
let [host, port] = parseHost(config);

donny({ port, bind: host, webroot }).
then(() => {
console.error(`serving ${repr(webroot)} at http://${host}:${port}`);
});
};

exports.live = (config, root) => {
let liveServer = loadExtension("live-server", "failed to activate live-server");
let [host, port] = parseHost(config);

liveServer.start({ port, host, root, open: false });
};
export async function liveServer(config, root) {
let server = await loadExtension("live-server", "failed to activate live-server");
let [host, port] = _parseHost(config);
server.start({ port, host, root, open: false });
}

exports._parseHost = parseHost;
export async function staticServer(config, webroot) {
let donny = await loadExtension("donny", "failed to activate server");
let [host, port] = _parseHost(config);
await donny({ port, bind: host, webroot });
console.error(`serving ${repr(webroot)} at http://${host}:${port}`);
}

function parseHost(config) {
export function _parseHost(config) {
let { host, port } = DEFAULTS;
if(config === true) {
return [host, port];
Expand Down
8 changes: 4 additions & 4 deletions lib/util/files/finder.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
let { readdir, stat } = require("fs/promises");
let path = require("path");
import { readdir, stat } from "fs/promises";
import path from "path";

module.exports = class FileFinder {
export class FileFinder {
constructor(directory, { skipDotfiles, filter = () => true } = {}) {
this.directory = directory;
this.filter = filename => {
Expand All @@ -23,7 +23,7 @@ module.exports = class FileFinder {
return filesWithinDirectory(this.directory, filepaths).
then(filepaths => filepaths.filter(this.filter));
}
};
}

function tree(filepath, referenceDir = filepath) {
return stat(filepath).
Expand Down
12 changes: 5 additions & 7 deletions lib/util/files/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"use strict";

let { writeFile } = require("fs/promises");
let { mkdirSync } = require("fs");
let path = require("path");
import { writeFile } from "fs/promises";
import { mkdirSync } from "fs";
import path from "path";

let KNOWN = new Set(); // avoids redundant `mkdir` invocations
let LOCKS = new Map();

// avoids concurrent write operations and creates target directory if necessary
module.exports = function createFile(filepath, contents) {
export function createFile(filepath, contents) {
let lock = LOCKS.get(filepath);
if(lock) { // defer
return lock.then(() => createFile(filepath, contents));
Expand All @@ -26,4 +24,4 @@ module.exports = function createFile(filepath, contents) {
return prom.then(() => {
LOCKS.delete(filepath);
});
};
}
Loading