Added imports

pull/109/head 0.1.1
Zef Hemel 2022-10-13 15:16:18 +02:00
parent 4c19ab21f2
commit 982623fc38
21 changed files with 120 additions and 46 deletions

View File

@ -16,8 +16,8 @@ github:
tasks: tasks:
- name: Setup - name: Setup
init: | init: |
deno task install
deno task build deno task build
deno task install
gp sync-done setup gp sync-done setup
- name: Server watcher - name: Server watcher
init: | init: |

View File

@ -1,3 +1,3 @@
#!/bin/sh #!/bin/sh
deno run -A --unstable plugos/bin/plugos-bundle.ts --dist dist_bundle/_plug $1 --exclude=https://esm.sh/handlebars,https://deno.land/std/encoding/yaml.ts,https://esm.sh/@lezer/lr plugs/*/*.plug.yaml deno run -A --unstable plugos/bin/plugos-bundle.ts --dist dist_bundle/_plug $@ --exclude=https://esm.sh/handlebars,https://deno.land/std/encoding/yaml.ts,https://esm.sh/@lezer/lr plugs/*/*.plug.yaml

View File

@ -1,7 +1,10 @@
import { base64Decode, base64Encode } from "./base64.ts"; import { base64Decode, base64Encode } from "./base64.ts";
import { mime } from "../deps.ts"; import { mime } from "../deps.ts";
export type AssetJson = Record<string, string>; type DataUrl = string;
// Mapping from path -> `data:mimetype;base64,base64-encoded-data` strings
export type AssetJson = Record<string, DataUrl>;
export class AssetBundle { export class AssetBundle {
readonly bundle: AssetJson; readonly bundle: AssetJson;

View File

@ -8,9 +8,8 @@ import {
esbuild, esbuild,
sandboxCompileModule, sandboxCompileModule,
} from "../compile.ts"; } from "../compile.ts";
import { path } from "../../server/deps.ts"; import { cacheDir, flags, path } from "../deps.ts";
import * as flags from "https://deno.land/std@0.158.0/flags/mod.ts";
import { bundleAssets } from "../asset_bundle/builder.ts"; import { bundleAssets } from "../asset_bundle/builder.ts";
export async function bundle( export async function bundle(
@ -26,14 +25,9 @@ export async function bundle(
throw new Error(`Missing 'name' in ${manifestPath}`); throw new Error(`Missing 'name' in ${manifestPath}`);
} }
const allModulesToExclude = options.excludeModules
? options.excludeModules.slice()
: [];
// Dependencies // Dependencies
for (let [name, moduleSpec] of Object.entries(manifest.dependencies || {})) { for (let [name, moduleSpec] of Object.entries(manifest.dependencies || {})) {
manifest.dependencies![name] = await sandboxCompileModule(moduleSpec); manifest.dependencies![name] = await sandboxCompileModule(moduleSpec);
allModulesToExclude.push(name);
} }
// Assets // Assets
@ -43,9 +37,38 @@ export async function bundle(
); );
manifest.assets = assetsBundle.toJSON(); manifest.assets = assetsBundle.toJSON();
// Functions // Imports
// Imports currently only "import" dependencies at this point, importing means: assume they're preloaded so we don't need to bundle them
const plugCache = path.join(cacheDir()!, "plugos");
await Deno.mkdir(plugCache, { recursive: true });
const imports: Manifest<any>[] = [];
for (const manifestUrl of manifest.imports || []) {
// Safe file name
const cachedManifestPath = manifestUrl.replaceAll(/[^a-zA-Z0-9]/g, "_");
try {
if (options.reload) {
throw new Error("Forced reload");
}
// Try to just load from the cache
const cachedManifest = JSON.parse(
await Deno.readTextFile(path.join(plugCache, cachedManifestPath)),
) as Manifest<any>;
imports.push(cachedManifest);
} catch {
// Otherwise, download and cache
console.log("Caching plug", manifestUrl, "to", plugCache);
const cachedManifest = await (await fetch(manifestUrl))
.json() as Manifest<any>;
await Deno.writeTextFile(
path.join(plugCache, cachedManifestPath),
JSON.stringify(cachedManifest),
);
imports.push(cachedManifest);
}
}
for (let [name, def] of Object.entries(manifest.functions || {})) { // Functions
for (const def of Object.values(manifest.functions || {})) {
let jsFunctionName = "default", let jsFunctionName = "default",
filePath = path.join(rootPath, def.path!); filePath = path.join(rootPath, def.path!);
if (filePath.indexOf(":") !== -1) { if (filePath.indexOf(":") !== -1) {
@ -57,7 +80,12 @@ export async function bundle(
jsFunctionName, jsFunctionName,
{ {
...options, ...options,
excludeModules: allModulesToExclude, imports: [
manifest,
...imports,
// This is mostly for testing
...options.imports || [],
],
}, },
); );
delete def.path; delete def.path;
@ -130,15 +158,14 @@ async function bundleRun(
if (import.meta.main) { if (import.meta.main) {
const args = flags.parse(Deno.args, { const args = flags.parse(Deno.args, {
boolean: ["debug", "watch"], boolean: ["debug", "watch", "reload", "info"],
string: ["dist", "exclude", "importmap"], string: ["dist", "importmap"],
alias: { w: "watch" }, alias: { w: "watch" },
// collect: ["exclude"],
}); });
if (args._.length === 0) { if (args._.length === 0) {
console.log( console.log(
"Usage: plugos-bundle [--debug] [--dist <path>] [--importmap import_map.json] [--exclude=package1,package2] <manifest.plug.yaml> <manifest2.plug.yaml> ...", "Usage: plugos-bundle [--debug] [--reload] [--dist <path>] [--info] [--importmap import_map.json] [--exclude=package1,package2] <manifest.plug.yaml> <manifest2.plug.yaml> ...",
); );
Deno.exit(1); Deno.exit(1);
} }
@ -153,7 +180,8 @@ if (import.meta.main) {
args.watch, args.watch,
{ {
debug: args.debug, debug: args.debug,
excludeModules: args.exclude ? args.exclude.split(",") : undefined, reload: args.reload,
info: args.info,
importMap: args.importmap importMap: args.importmap
? new URL(args.importmap, `file://${Deno.cwd()}/`) ? new URL(args.importmap, `file://${Deno.cwd()}/`)
: undefined, : undefined,

View File

@ -9,14 +9,33 @@ export const esbuild: typeof esbuildWasm = Deno.run === undefined
import { path } from "./deps.ts"; import { path } from "./deps.ts";
import { denoPlugin } from "./forked/esbuild_deno_loader/mod.ts"; import { denoPlugin } from "./forked/esbuild_deno_loader/mod.ts";
import { patchDenoLibJS } from "./hack.ts"; import { patchDenoLibJS } from "./hack.ts";
import { Manifest } from "./types.ts";
export type CompileOptions = { export type CompileOptions = {
debug?: boolean; debug?: boolean;
excludeModules?: string[]; imports?: Manifest<any>[];
meta?: boolean;
importMap?: URL; importMap?: URL;
// Reload plug import cache
reload?: boolean;
// Print info on bundle size
info?: boolean;
}; };
function esBuildExternals(imports?: Manifest<any>[]) {
if (!imports) {
return [];
}
const externals: string[] = [];
for (const manifest of imports) {
for (const dep of Object.keys(manifest.dependencies || {})) {
if (!externals.includes(dep)) {
externals.push(dep);
}
}
}
return externals;
}
export async function compile( export async function compile(
filePath: string, filePath: string,
functionName: string | undefined = undefined, functionName: string | undefined = undefined,
@ -49,8 +68,8 @@ export async function compile(
sourcemap: false, //debug ? "inline" : false, sourcemap: false, //debug ? "inline" : false,
minify: !options.debug, minify: !options.debug,
outfile: outFile, outfile: outFile,
metafile: true, metafile: options.info,
external: options.excludeModules || [], external: esBuildExternals(options.imports),
treeShaking: true, treeShaking: true,
plugins: [ plugins: [
denoPlugin({ denoPlugin({
@ -62,8 +81,8 @@ export async function compile(
absWorkingDir: path.resolve(path.dirname(inFile)), absWorkingDir: path.resolve(path.dirname(inFile)),
}); });
if (options.meta) { if (options.info) {
const text = await esbuild.analyzeMetafile(result.metafile); const text = await esbuild.analyzeMetafile(result.metafile!);
console.log("Bundle info for", functionName, text); console.log("Bundle info for", functionName, text);
} }

View File

@ -2,3 +2,5 @@ export { globToRegExp } from "https://deno.land/std@0.158.0/path/glob.ts";
export { walk } from "https://deno.land/std@0.159.0/fs/mod.ts"; export { walk } from "https://deno.land/std@0.159.0/fs/mod.ts";
export * as path from "https://deno.land/std@0.158.0/path/mod.ts"; export * as path from "https://deno.land/std@0.158.0/path/mod.ts";
export { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts"; export { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";
export { default as cacheDir } from "https://deno.land/x/cache_dir@0.2.0/mod.ts";
export * as flags from "https://deno.land/std@0.158.0/flags/mod.ts";

View File

@ -1,3 +1,4 @@
// IMPORTANT: After modifiying this file, run `deno task generate` in the SB root to regenerate the asset bundle (`worker_bundle.json`), which will be imported for the runtime.
import { safeRun } from "../util.ts"; import { safeRun } from "../util.ts";
import { ConsoleLogger } from "./custom_logger.ts"; import { ConsoleLogger } from "./custom_logger.ts";
import type { ControllerMessage, WorkerMessage } from "./worker.ts"; import type { ControllerMessage, WorkerMessage } from "./worker.ts";
@ -18,8 +19,9 @@ if (typeof Deno === "undefined") {
}; };
} }
let loadedFunctions = new Map<string, Function>(); // deno-lint-ignore ban-types
let pendingRequests = new Map< const loadedFunctions = new Map<string, Function>();
const pendingRequests = new Map<
number, number,
{ {
resolve: (result: unknown) => void; resolve: (result: unknown) => void;
@ -55,7 +57,7 @@ self.syscall = async (name: string, ...args: any[]) => {
}); });
}; };
let loadedModules = new Map<string, any>(); const loadedModules = new Map<string, any>();
// @ts-ignore: global to load dynamic imports // @ts-ignore: global to load dynamic imports
self.require = (moduleName: string): any => { self.require = (moduleName: string): any => {
@ -69,7 +71,7 @@ self.require = (moduleName: string): any => {
return mod; return mod;
}; };
// @ts-ignore // @ts-ignore: global overwrite on purpose
self.console = new ConsoleLogger((level, message) => { self.console = new ConsoleLogger((level, message) => {
workerPostMessage({ type: "log", level, message }); workerPostMessage({ type: "log", level, message });
}, false); }, false);
@ -80,7 +82,7 @@ function wrapScript(code: string) {
self.addEventListener("message", (event: { data: WorkerMessage }) => { self.addEventListener("message", (event: { data: WorkerMessage }) => {
safeRun(async () => { safeRun(async () => {
let data = event.data; const data = event.data;
switch (data.type) { switch (data.type) {
case "load": case "load":
{ {

View File

@ -1,11 +1,13 @@
import { Manifest, RuntimeEnvironment } from "./types.ts"; import { Manifest, RuntimeEnvironment } from "./types.ts";
import { Sandbox } from "./sandbox.ts"; import { Sandbox } from "./sandbox.ts";
import { System } from "./system.ts"; import { System } from "./system.ts";
import { AssetBundle, AssetJson } from "./asset_bundle/bundle.ts";
export class Plug<HookT> { export class Plug<HookT> {
system: System<HookT>; system: System<HookT>;
sandbox: Sandbox; sandbox: Sandbox;
public manifest?: Manifest<HookT>; public manifest?: Manifest<HookT>;
public assets?: AssetBundle;
readonly runtimeEnv: RuntimeEnvironment; readonly runtimeEnv: RuntimeEnvironment;
grantedPermissions: string[] = []; grantedPermissions: string[] = [];
name: string; name: string;
@ -25,6 +27,9 @@ export class Plug<HookT> {
async load(manifest: Manifest<HookT>) { async load(manifest: Manifest<HookT>) {
this.manifest = manifest; this.manifest = manifest;
this.assets = new AssetBundle(
manifest.assets ? manifest.assets as AssetJson : {},
);
// TODO: These need to be explicitly granted, not just taken // TODO: These need to be explicitly granted, not just taken
this.grantedPermissions = manifest.requiredPermissions || []; this.grantedPermissions = manifest.requiredPermissions || [];
for (const [dep, code] of Object.entries(manifest.dependencies || {})) { for (const [dep, code] of Object.entries(manifest.dependencies || {})) {

View File

@ -122,19 +122,18 @@ Deno.test("Run a deno sandbox", async () => {
import { bundle as plugOsBundle } from "./bin/plugos-bundle.ts"; import { bundle as plugOsBundle } from "./bin/plugos-bundle.ts";
import { esbuild } from "./compile.ts"; import { esbuild } from "./compile.ts";
import { AssetBundle } from "./asset_bundle/bundle.ts";
const __dirname = new URL(".", import.meta.url).pathname; const __dirname = new URL(".", import.meta.url).pathname;
Deno.test("Preload dependencies", async () => { Deno.test("Preload dependencies", async () => {
const globalModules = await plugOsBundle( const globalModules = await plugOsBundle(
`${__dirname}../plugs/global.plug.yaml`, `${__dirname}../plugs/global.plug.yaml`,
); );
// const globalModules = JSON.parse(
// Deno.readTextFileSync(`${tmpDist}/global.plug.json`),
// );
const testPlugManifest = await plugOsBundle( const testPlugManifest = await plugOsBundle(
`${__dirname}test.plug.yaml`, `${__dirname}test.plug.yaml`,
{ excludeModules: Object.keys(globalModules.dependencies!) }, {
imports: [globalModules],
},
); );
esbuild.stop(); esbuild.stop();

View File

@ -1,5 +1,4 @@
import { SysCallMapping, System } from "../system.ts"; import { SysCallMapping, System } from "../system.ts";
import { AssetBundle } from "../asset_bundle/bundle.ts";
export default function assetSyscalls(system: System<any>): SysCallMapping { export default function assetSyscalls(system: System<any>): SysCallMapping {
return { return {
@ -7,8 +6,9 @@ export default function assetSyscalls(system: System<any>): SysCallMapping {
ctx, ctx,
name: string, name: string,
): string => { ): string => {
return (system.loadedPlugs.get(ctx.plug.name)!.manifest! return system.loadedPlugs.get(ctx.plug.name)!.assets!.readFileAsDataUrl(
.assets as AssetBundle).readFileAsDataUrl(name); name,
);
}, },
}; };
} }

View File

@ -1,17 +1,16 @@
import { sandboxCompile, sandboxCompileModule } from "../compile.ts"; import { sandboxCompile, sandboxCompileModule } from "../compile.ts";
import { SysCallMapping } from "../system.ts"; import { SysCallMapping } from "../system.ts";
import { Manifest } from "../types.ts";
// TODO: FIgure out a better way to do this export function esbuildSyscalls(
const builtinModules = ["yaml", "handlebars"]; imports: Manifest<any>[],
): SysCallMapping {
export function esbuildSyscalls(): SysCallMapping {
return { return {
"esbuild.compile": async ( "esbuild.compile": async (
_ctx, _ctx,
filename: string, filename: string,
code: string, code: string,
functionName?: string, functionName?: string,
excludeModules: string[] = [],
): Promise<string> => { ): Promise<string> => {
return await sandboxCompile( return await sandboxCompile(
filename, filename,
@ -19,7 +18,7 @@ export function esbuildSyscalls(): SysCallMapping {
functionName, functionName,
{ {
debug: true, debug: true,
excludeModules: [...builtinModules, ...excludeModules], imports,
}, },
); );
}, },
@ -28,7 +27,7 @@ export function esbuildSyscalls(): SysCallMapping {
moduleName: string, moduleName: string,
): Promise<string> => { ): Promise<string> => {
return await sandboxCompileModule(moduleName, { return await sandboxCompileModule(moduleName, {
excludeModules: builtinModules, imports,
}); });
}, },
}; };

View File

@ -4,6 +4,8 @@ import { AssetJson } from "./asset_bundle/bundle.ts";
export interface Manifest<HookT> { export interface Manifest<HookT> {
name: string; name: string;
requiredPermissions?: string[]; requiredPermissions?: string[];
// URLs to plugs whose dependencies are presumed to already be loaded (main use case: global.plug.json)
imports?: string[];
assets?: string[] | AssetJson; assets?: string[] | AssetJson;
dependencies?: { dependencies?: {
[key: string]: string; [key: string]: string;

View File

@ -1,4 +1,6 @@
name: core name: core
imports:
- https://get.silverbullet.md/global.plug.json
syntax: syntax:
Hashtag: Hashtag:
firstCharacters: firstCharacters:

View File

@ -1,4 +1,6 @@
name: emoji name: emoji
imports:
- https://get.silverbullet.md/global.plug.json
functions: functions:
emojiCompleter: emojiCompleter:
path: "./emoji.ts:emojiCompleter" path: "./emoji.ts:emojiCompleter"

View File

@ -1,4 +1,6 @@
name: markdown name: markdown
imports:
- https://get.silverbullet.md/global.plug.json
functions: functions:
toggle: toggle:
path: "./markdown.ts:togglePreview" path: "./markdown.ts:togglePreview"

View File

@ -1,4 +1,6 @@
name: plugmd name: plugmd
imports:
- https://get.silverbullet.md/global.plug.json
functions: functions:
check: check:
path: "./plugmd.ts:checkCommand" path: "./plugmd.ts:checkCommand"

View File

@ -1,4 +1,6 @@
name: query name: query
imports:
- https://get.silverbullet.md/global.plug.json
functions: functions:
updateMaterializedQueriesOnPage: updateMaterializedQueriesOnPage:
path: ./materialized_queries.ts:updateMaterializedQueriesOnPage path: ./materialized_queries.ts:updateMaterializedQueriesOnPage

View File

@ -1,4 +1,6 @@
name: tasks name: tasks
imports:
- https://get.silverbullet.md/global.plug.json
syntax: syntax:
DeadlineDate: DeadlineDate:
firstCharacters: firstCharacters:

View File

@ -107,7 +107,7 @@ export class HttpServer {
spaceSyscalls(this.space), spaceSyscalls(this.space),
eventSyscalls(this.eventHook), eventSyscalls(this.eventHook),
markdownSyscalls(buildMarkdown([])), markdownSyscalls(buildMarkdown([])),
esbuildSyscalls(), esbuildSyscalls([this.globalModules]),
systemSyscalls(this), systemSyscalls(this),
sandboxSyscalls(this.system), sandboxSyscalls(this.system),
assetSyscalls(this.system), assetSyscalls(this.system),

View File

@ -17,6 +17,9 @@
// return undefined; // return undefined;
}, },
}, },
errors: {
AlreadyExists: class extends Error {},
}
}; };
</script> </script>
<style> <style>