Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@b9g/shovel

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@b9g/shovel - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

fixtures/dependency.js

18

bin/shovel.js
#!/usr/bin/env node --experimental-vm-modules --experimental-fetch --no-warnings
//#!/usr/bin/env node --experimental-vm-modules --experimental-fetch --no-warnings --inspect --inspect-brk
// TODO: Figure out how to pass node flags. Is that even possible?
// TODO: Squash warnings from process

@@ -7,5 +8,6 @@ // https://github.com/yarnpkg/berry/blob/2cf0a8fe3e4d4bd7d4d344245d24a85a45d4c5c9/packages/yarnpkg-pnp/sources/loader/applyPatch.ts#L414-L435

import {Command} from "commander";
import whyIsNodeRunning from "why-is-node-running";
import pkg from "../package.json" assert {type: "json"};
import develop from "../src/develop.js";
const program = new Command();

@@ -18,5 +20,17 @@ program

program.command("develop <file>")
.description("Start a development server.")
.option("-p, --port <port>", "Port to listen on", "1337")
.action(develop);
.action(async (file, options) => {
const {develop} = await import("../src/develop.js");
await develop(file, options);
});
program.command("static <file>")
.description("Build a static site.")
.option("--out-dir <dir>", "Output directory", "dist")
.action(async (file, options) => {
const {static_} = await import("../src/static.js");
await static_(file, options);
});
await program.parseAsync(process.argv);

19

package.json
{
"name": "@b9g/shovel",
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"description": "Dig for treasure",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node --experimental-fetch $(npm bin)/uvu"
},
"license": "MIT",
"dependencies": {
"@b9g/crank": "^0.5.3",
"@emotion/css": "^11.10.6",
"@pkgjs/parseargs": "^0.11.0",
"@repeaterjs/repeater": "^3.0.4",
"chokidar": "^3.5.3",
"commander": "^10.0.0",
"esbuild": "^0.17.11",

@@ -28,3 +22,12 @@ "is-core-module": "^2.11.0",

"access": "public"
},
"devDependencies": {
"@b9g/crank": "^0.5.3",
"@emotion/css": "^11.10.6",
"uvu": "^0.5.6",
"sinon": "^15.0.2",
"why-is-node-running": "^2.2.2",
"commander": "^10.0.0",
"fkill": "^8.1.0"
}
}

@@ -5,60 +5,90 @@ import * as Path from "path";

import * as VM from "vm";
import * as ESBuild from "esbuild";
import MagicString from "magic-string";
import {Repeater} from "@repeaterjs/repeater";
import {isPathSpecifier, resolve} from "./resolve.js";
import {formatMessages} from "esbuild";
import * as Resolve from "./resolve.js";
import {createFetchServer} from "./server.js";
import {Hot, disposeHot} from "./hot.js";
import {Watcher} from "./_esbuild.js";
function watch(entry, watcherCache = new Map()) {
return new Repeater(async (push, stop) => {
if (watcherCache.has(entry)) {
push((await watcherCache.get(entry)).result);
stop();
return;
//interface ModuleCacheValue {
// module: VM.SourceTextModule;
// dependents: Set<string>;
// hot: Hot;
//}
const moduleCache = new Map();
function createLink(watcher) {
return async function link(specifier, referencingModule) {
const basedir = Path.dirname(fileURLToPath(referencingModule.identifier));
// TODO: Let’s try to use require.resolve() here.
const resolved = await Resolve.resolve(specifier, basedir);
if (Resolve.isPathSpecifier(specifier)) {
const url = pathToFileURL(resolved).href;
const result = await watcher.build(resolved);
const code = result.outputFiles.find((file) => file.path.endsWith(".js"))?.text || "";
// We don’t have to link this module because it will be linked by the
// root module.
if (moduleCache.has(resolved)) {
moduleCache.get(resolved).dependents.add(fileURLToPath(referencingModule.identifier));
return moduleCache.get(resolved).module;
}
// TODO: We need to cache modules
const module = new VM.SourceTextModule(code, {
identifier: url,
initializeImportMeta(meta) {
meta.url = url;
},
async importModuleDynamically(specifier, referencingModule) {
const linked = await link(specifier, referencingModule);
await linked.link(link);
await linked.evaluate();
return linked;
},
});
moduleCache.set(resolved, {
module,
dependents: new Set([fileURLToPath(referencingModule.identifier)])
});
return module;
} else {
// This is a bare module specifier so we import from node modules.
const namespace = await import(resolved);
const exports = Object.keys(namespace);
return new VM.SyntheticModule(exports, function () {
for (const key of exports) {
this.setExport(key, namespace[key]);
}
});
}
};
}
let resolve;
watcherCache.set(entry, {
result: new Promise((r) => (resolve = r)),
});
const watchPlugin = {
name: "watch",
setup(build) {
// This is called every time a module is edited.
build.onEnd(async (result) => {
resolve(result);
push(result);
watcherCache.set(entry, {result});
});
},
};
const ctx = await ESBuild.context({
format: "esm",
platform: "node",
entryPoints: [entry],
//bundle: true,
bundle: false,
metafile: true,
write: false,
packages: "external",
sourcemap: "both",
plugins: [watchPlugin],
// We need this to export map files.
outdir: "dist",
logLevel: "silent",
});
export async function develop(file, options) {
file = Path.resolve(process.cwd(), file);
const port = parseInt(options.port);
if (Number.isNaN(port)) {
throw new Error("Invalid port", options.port);
}
await ctx.watch();
await stop;
ctx.dispose();
watcherCache.delete(entry);
process.on("uncaughtException", (err) => {
console.error(err);
});
}
export default async function develop(file, options) {
const url = pathToFileURL(file).href;
const port = parseInt(options.port);
process.on("unhandledRejection", (err) => {
console.error(err);
});
process.on("SIGINT", async () => {
server.close();
await watcher.dispose();
process.exit(0);
});
process.on("SIGTERM", async () => {
server.close();
await watcher.dispose();
process.exit(0);
});
let namespace = null;
let hot = null;
const server = createFetchServer(async function fetcher(req) {

@@ -85,102 +115,66 @@ if (typeof namespace?.default?.fetch === "function") {

process.on("uncaughtException", (err) => {
console.error(err);
});
process.on("unhandledRejection", (err) => {
console.error(err);
});
const watcherCache = new Map();
for await (const result of watch(file, watcherCache)) {
if (result.errors && result.errors.length) {
const formatted = await ESBuild.formatMessages(result.errors, {
const watcher = new Watcher(async (record, watcher) => {
if (record.result.errors.length > 0) {
const formatted = await formatMessages(record.result.errors, {
kind: "error",
color: true,
});
console.error(formatted.join("\n"));
continue;
} else if (record.result.warnings.length > 0) {
const formatted = await formatMessages(record.result.warnings, {
kind: "warning",
});
console.warn(formatted.join("\n"));
}
const code = result.outputFiles.find((file) => file.path.endsWith(".js"))?.text;
const map = result.outputFiles.find((file) => file.path.endsWith(".map"))?.text;
// TODO: Refactor by moving link and reloadRootModule to the top-level scope.
async function link(specifier, referencingModule) {
const basedir = Path.dirname(fileURLToPath(referencingModule.identifier));
const resolved = await resolve(specifier, basedir);
if (isPathSpecifier(specifier)) {
const firstResult = await new Promise(async (resolve) => {
let initial = true;
for await (const result of watch(resolved, watcherCache)) {
if (initial) {
initial = false;
resolve(result);
} else {
// TODO: Allow import.meta.hot.accept to be called and prevent
// reloading the root module.
await reloadRootModule();
}
// TODO: Rather than reloading the root module, we should bubble changes
// from dependencies to dependents according to import.meta.hot
if (!record.initial) {
const queue = [record.entry];
while (queue.length > 0) {
const entry = queue.shift();
const dependents = moduleCache.get(entry)?.dependents;
if (dependents) {
for (const dependent of dependents) {
queue.push(dependent);
}
});
}
const code = firstResult.outputFiles.find((file) => file.path.endsWith(".js"))?.text;
const depURL = pathToFileURL(resolved).href;
return new VM.SourceTextModule(code || "", {
identifier: depURL,
initializeImportMeta(meta) {
meta.url = depURL;
meta.hot = hot;
},
async importModuleDynamically(specifier, module) {
const linked = await link(specifier, module);
await linked.link(link);
await linked.evaluate();
return linked;
},
});
moduleCache.delete(entry);
}
const child = await import(resolved);
const exports = Object.keys(child);
return new VM.SyntheticModule(exports, function () {
for (const key of exports) {
this.setExport(key, child[key]);
}
});
const rootResult = await watcher.build(file);
await reload(rootResult);
}
});
async function reloadRootModule() {
if (hot) {
disposeHot(hot);
}
const link = createLink(watcher);
async function reload(result) {
const code = result.outputFiles.find((file) => file.path.endsWith(".js"))?.text || "";
const url = pathToFileURL(file).href;
const module = new VM.SourceTextModule(code, {
identifier: url,
initializeImportMeta(meta) {
meta.url = url;
},
async importModuleDynamically(specifier, referencingModule) {
const linked = await link(specifier, referencingModule);
await linked.link(link);
await linked.evaluate();
return linked;
},
});
const module = new VM.SourceTextModule(code, {
identifier: url,
initializeImportMeta(meta) {
meta.url = url;
meta.hot = hot;
},
async importModuleDynamically(specifier, module) {
const linked = await link(specifier, module);
await linked.link(link);
await linked.evaluate();
return linked;
},
});
try {
await module.link(link);
await module.evaluate();
} catch (err) {
console.error(err);
return;
}
try {
await module.link(link);
await module.evaluate();
namespace = module.namespace;
hot = new Hot();
console.info("rebuilt:", url);
} catch (err) {
console.error(err);
namespace = null;
}
}
await reloadRootModule();
}
const result = await watcher.build(file);
await reload(result);
return server;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc