Skip to content

Commit

Permalink
feat: pkg versioning using proper package.json
Browse files Browse the repository at this point in the history
bundle.js.org now actively loads up the package.json for individual packages to ensure proper dependency versions. Switched to using unpkg.com as default CDN, because it's easier to traverse
  • Loading branch information
okikio committed Feb 24, 2022
1 parent efbeb32 commit c747ed9
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 49 deletions.
104 changes: 65 additions & 39 deletions src/ts/plugins/cdn.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,77 @@

import type { Plugin } from 'esbuild';
import type { OnResolveArgs, OnResolveResult, Plugin } from 'esbuild';

import { fetchPkg } from './http';
import { inferLoader, isBareImport, getCDNHost } from '../util/loader';
import { HTTP_NAMESPACE } from './http';
import { isBareImport, getCDNHost } from '../util/loader';
import { resolveImports } from '../util/resolve-imports';

import { resolve, legacy } from "resolve.exports";
import { parse as parsePackageName } from "parse-package-name";
import { getRequest } from '../util/cache';

export const CDN_RESOLVE = (host?: string): (args: OnResolveArgs) => OnResolveResult | Promise<OnResolveResult> => {
return async (args) => {
if (isBareImport(args.path)) {
// Heavily based off of https://github.com/egoist/play-esbuild/blob/main/src/lib/esbuild.ts
const parsed = parsePackageName(args.path);
let subpath = parsed.path;
let pkg = args.pluginData?.pkg ?? {};
let path;

// Resolving imports from package.json, if a package starts with "#"
if (args.path[0] == "#") {
let path = resolveImports({ ...pkg, exports: pkg.imports }, args.path, {
require: args.kind === "require-call" || args.kind === "require-resolve"
});

if (typeof path === "string") {
subpath = path.replace(/^\.?\/?/, "/");

if (subpath && subpath[0] !== "/")
subpath = `/${subpath}`;

let { url } = getCDNHost(`${pkg.name}@${pkg.version}${subpath}`);
return {
namespace: HTTP_NAMESPACE,
path: url,
pluginData: { pkg }
};
}
}

if (!subpath) {
let { url } = getCDNHost(`${parsed.name}@${parsed.version}/package.json`, host);

// Strongly cache package.json files
pkg = await getRequest(url, true).then((res) => res.json());
path = resolve(pkg, ".", {
require: args.kind === "require-call" || args.kind === "require-resolve",
}) || legacy(pkg);

if (typeof path === "string")
subpath = path.replace(/^\.?\/?/, "/");
}

if (subpath && subpath[0] !== "/")
subpath = `/${subpath}`;

let { url } = getCDNHost(`${parsed.name}@${parsed.version}${subpath}`);
return {
namespace: HTTP_NAMESPACE,
path: url,
pluginData: { pkg }
};
}
};
}

export const CDN_NAMESPACE = 'cdn-url';
export const CDN = (): Plugin => {
return {
name: CDN_NAMESPACE,
setup(build) {
// Resolve bare imports to the CDN required using different URL schemes
build.onResolve({ filter: /.*/ }, (args) => {
if (isBareImport(args.path)) {
let { host, argPath } = getCDNHost(args.path);
return {
namespace: CDN_NAMESPACE,
path: argPath,
pluginData: {
parentUrl: host,
},
};
}
});

// Pass on the info from the bare import
build.onResolve({ namespace: CDN_NAMESPACE, filter: /.*/ }, async (args) => {
return {
path: args.path,
namespace: CDN_NAMESPACE,
pluginData: args.pluginData,
};
});

// On load
build.onLoad({ namespace: CDN_NAMESPACE, filter: /.*/ }, async (args) => {
let pathUrl = new URL(args.path, args.pluginData.parentUrl).toString();
pathUrl = pathUrl.replace(/\/$/, "/index"); // Some packages use "../../" which this is supposed to fix

const { content, url } = await fetchPkg(pathUrl);
return Object.assign({
contents: content,
pluginData: {
parentUrl: url,
},
loader: inferLoader(pathUrl)
});
});
build.onResolve({ filter: /.*/ }, CDN_RESOLVE());
},
};
};
16 changes: 14 additions & 2 deletions src/ts/plugins/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,25 @@ export const PolyfillMap = {
process: 'process/browser',
fs: 'memfs',
os: 'os-browserify/browser',
'v8': "v8",
"node-inspect": "node-inspect",
"_linklist": "_linklist",
"_stream_wrap": "_stream_wrap"
};

export const PolyfillKeys = Object.keys(PolyfillMap);

export const DeprecatedAPIs = ["v8/tools/codemap", "v8/tools/consarray", "v8/tools/csvparser", "v8/tools/logreader", "v8/tools/profile_view", "v8/tools/profile", "v8/tools/SourceMap", "v8/tools/splaytree", "v8/tools/tickprocessor-driver", "v8/tools/tickprocessor", "node-inspect/lib/_inspect", "node-inspect/lib/internal/inspect_client ", "node-inspect/lib/internal/inspect_repl", "_linklist", "_stream_wrap"];
export const ExternalPackages = ['chokidar', 'yargs', 'fsevents', `worker_threads`, "assert/strict", "async_hooks", "diagnostics_channel", "http2", "fs/promises", "inspector", "perf_hooks", "timers/promises", "trace_events", "v8", "wasi", ...DeprecatedAPIs, ...PolyfillKeys];

// Based on https://github.com/egoist/play-esbuild/blob/7e34470f9e6ddcd9376704cd8b988577ddcd46c9/src/lib/esbuild.ts#L51
export const isExternal = (id: string) => {
return ExternalPackages.find((it: string): boolean => {
if (it === id) return true; // import 'foo' & external: ['foo']
if (id.startsWith(`${it}/`)) return true; // import 'foo/bar.js' & external: ['foo']
return false;
});
}

export const EXTERNALS_NAMESPACE = 'external-globals';
export const EXTERNAL = (): Plugin => {
return {
Expand All @@ -66,7 +78,7 @@ export const EXTERNAL = (): Plugin => {

build.onResolve({ filter: /.*/ }, (args) => {
let path = args.path.replace(/^node\:/, "");
if (ExternalPackages.includes(path)) {
if (isExternal(path)) {
return {
path,
namespace: EXTERNALS_NAMESPACE,
Expand Down
35 changes: 27 additions & 8 deletions src/ts/plugins/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
import type { Plugin } from 'esbuild';

import { getRequest } from '../util/cache';
import { inferLoader } from '../util/loader';
import { getCDNHost, inferLoader, isBareImport } from '../util/loader';

import { urlJoin } from "../util/path";
import { CDN_RESOLVE } from './cdn';

export async function fetchPkg(url: string) {
let response = await getRequest(url);
if (!response.ok)
throw new Error(`[getRequest] Failed to load ${response.url}: ${response.status}`)
return {
url: response.url,
content: new Uint8Array(await response.arrayBuffer()),
};
}

export const HTTP_NAMESPACE = 'http-url';
export const HTTP = (): Plugin => {
export const HTTP = (logger: (messages: string[] | any, type?: "error" | "warning" | any) => void): Plugin => {
return {
name: HTTP_NAMESPACE,
setup(build) {
Expand All @@ -21,9 +27,8 @@ export const HTTP = (): Plugin => {
// Tag them with the "http-url" namespace to associate them with
// this plugin.
build.onResolve({ filter: /^https?:\/\// }, args => {
let resolveDir = args.resolveDir.replace(/^\//, '');
return {
path: resolveDir.length > 0 ? new URL(args.path, resolveDir).toString() : args.path,
path: args.path,
namespace: HTTP_NAMESPACE,
};
});
Expand All @@ -34,11 +39,24 @@ export const HTTP = (): Plugin => {
// the newly resolved URL in the "http-url" namespace so imports
// inside it will also be resolved as URLs recursively.
build.onResolve({ filter: /.*/, namespace: HTTP_NAMESPACE }, args => {
let importer = args.importer;
let pathUrl = args.path.replace(/\/$/, "/index"); // Some packages use "../../" which this is supposed to fix
let argPath = args.path.replace(/\/$/, "/index"); // Some packages use "../../" with the assumption that "/" is equal to "/index.js", this is supposed to fix that bug
if (!argPath.startsWith(".")) {
let { origin } = new URL(urlJoin(args.pluginData?.url, "../", argPath));
if (isBareImport(argPath)) {
return CDN_RESOLVE(origin)(args);
} else {
return {
path: getCDNHost(argPath, origin).url,
namespace: HTTP_NAMESPACE,
pluginData: { pkg: args.pluginData?.pkg },
};
}
}

return {
path: new URL(pathUrl, importer).toString(),
path: urlJoin(args.pluginData.url, "../", argPath),
namespace: HTTP_NAMESPACE,
pluginData: { pkg: args.pluginData?.pkg },
};
});

Expand All @@ -48,10 +66,11 @@ export const HTTP = (): Plugin => {
// would probably need to be more complex.
build.onLoad({ filter: /.*/, namespace: HTTP_NAMESPACE }, async (args) => {
const { content, url } = await fetchPkg(args.path);
logger("Fetch " + url, "info");
return {
contents: content,
loader: inferLoader(url),
resolveDir: `/${url}`, // a hack fix resolveDir problem
pluginData: { url, pkg: args.pluginData?.pkg }
};
});
},
Expand Down

0 comments on commit c747ed9

Please sign in to comment.