Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support importmap integrity field #363

Merged
merged 1 commit into from
Jul 7, 2024
Merged
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
3,356 changes: 1,195 additions & 2,161 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,12 @@
}
},
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/plugin-syntax-import-assertions": "^7.20.0",
"@babel/preset-typescript": "^7.18.6",
"@jspm/import-map": "^1.0.7",
"abort-controller": "^3.0.0",
"es-module-lexer": "^1.1.1",
"@babel/core": "^7.24.7",
"@babel/plugin-syntax-import-assertions": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@jspm/import-map": "^1.1.0",
"es-module-lexer": "^1.5.4",
"make-fetch-happen": "^8.0.14",
"rimraf": "^4.1.2",
"sver": "^1.8.4"
},
"devDependencies": {
Expand All @@ -58,7 +56,6 @@
"@types/vscode": "^1.75.1",
"@vscode/test-electron": "^2.2.3",
"chalk": "^4.1.2",
"chomp": "^0.2.17",
"cross-env": "^7.0.3",
"kleur": "^4.1.5",
"lit-element": "^2.5.1",
Expand Down
5 changes: 2 additions & 3 deletions src/common/fetch-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { wrapWithRetry, FetchFn } from "./fetch-common.js";
import path from "path";
import { homedir } from "os";
import process from "process";
import rimraf from "rimraf";
import makeFetchHappen from "make-fetch-happen";
import { readFileSync } from "fs";
import { readFileSync, rmdirSync } from "fs";
import { Buffer } from "buffer";

let cacheDir: string;
Expand All @@ -24,7 +23,7 @@ else
);

export function clearCache() {
rimraf.sync(path.join(cacheDir, "fetch-cache"));
rmdirSync(path.join(cacheDir, "fetch-cache"), { recursive: true });
}

const _fetch = makeFetchHappen.defaults({
Expand Down
13 changes: 5 additions & 8 deletions src/common/integrity.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
// @ts-ignore
import { fetch } from "#fetch";
import { createHash as _createHash } from "crypto";

let createHash = _createHash;

let createHash;
export function setCreateHash(_createHash) {
createHash = _createHash;
}

export async function getIntegrity(url, fetchOpts) {
if (!createHash) ({ createHash } = await import("crypto"));
const res = await fetch(url, fetchOpts);
const buf = await res.text();
export function getIntegrity(buf: Uint8Array | string): `sha384-${string}` {
const hash = createHash("sha384");
hash.update(buf);
return "sha384-" + hash.digest("base64");
return `sha384-${hash.digest("base64")}`;
}
2 changes: 0 additions & 2 deletions src/common/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ declare global {
var document: any;
// @ts-ignore
var location: any;
// @ts-ignore
var process: any;
}

export function isFetchProtocol(protocol) {
Expand Down
107 changes: 36 additions & 71 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ export interface GeneratorOptions {
* Disabled by default.
*/
typeScript?: boolean;

/**
* Whether to include "integrity" field in the import map
*/
integrity?: boolean;
}

export interface ModuleAnalysis {
Expand Down Expand Up @@ -344,6 +349,7 @@ export class Generator {
map: ImportMap;
logStream: LogStream;
log: Log;
integrity: boolean;

/**
* The number of concurrent installs the generator is busy processing.
Expand Down Expand Up @@ -391,6 +397,7 @@ export class Generator {
ignore = [],
commonJS = false,
typeScript = false,
integrity = false,
}: GeneratorOptions = {}) {
// Initialise the debug logger:
const { log, logStream } = createLogger();
Expand Down Expand Up @@ -451,6 +458,8 @@ export class Generator {
}
}

this.integrity = integrity;

// Initialise the resolver:
const resolver = new Resolver({ env, log, fetchOpts, preserveSymlinks: true, traceCjs: commonJS, traceTs: typeScript });
if (customProviders) {
Expand Down Expand Up @@ -486,14 +495,16 @@ export class Generator {
providers,
ignore,
resolutions,
commonJS,
commonJS
},
log,
resolver
);

// Reconstruct constraints and locks from the input map:
this.map = new ImportMap({ mapUrl: this.mapUrl, rootUrl: this.rootUrl });
if (!integrity)
this.map.integrity = {};
if (inputMap) this.addMappings(inputMap);
}

Expand Down Expand Up @@ -580,7 +591,7 @@ export class Generator {
} finally {
if (--this.installCnt === 0) {
const { map, staticDeps, dynamicDeps } =
await this.traceMap.finishInstall();
await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
if (!error) return { staticDeps, dynamicDeps };
}
Expand Down Expand Up @@ -657,7 +668,6 @@ export class Generator {
comment =
" Generated by @jspm/generator - https://github.com/jspm/generator ";
if (typeof htmlUrl === "string") htmlUrl = new URL(htmlUrl);
if (integrity) preload = true;
if (this.installCnt !== 0)
throw new JspmError(
"htmlInject cannot run alongside other install ops"
Expand All @@ -676,7 +686,8 @@ export class Generator {
var { map, staticDeps, dynamicDeps } = await this.extractMap(
modules,
htmlUrl,
rootUrl
rootUrl,
integrity
);
} catch (err) {
// Most likely cause of a generation failure:
Expand All @@ -685,10 +696,7 @@ export class Generator {
);
}

const preloadDeps =
(preload === true && integrity) || preload === "all"
? [...new Set([...staticDeps, ...dynamicDeps])]
: staticDeps;
const preloadDeps = preload === "all" ? [...new Set([...staticDeps, ...dynamicDeps])] : staticDeps;

const newlineTab = !whitespace
? analysis.newlineTab
Expand Down Expand Up @@ -742,9 +750,8 @@ export class Generator {
);

esms = `<script async src="${esmsUrl}" crossorigin="anonymous"${integrity
? ` integrity="${await getIntegrity(
esmsUrl,
this.traceMap.resolver.fetchOpts
? ` integrity="${getIntegrity(
new Uint8Array(await (await fetch(esmsUrl, this.traceMap.resolver.fetchOpts)).arrayBuffer())
)}"`
: ""
}></script>${newlineTab}`;
Expand All @@ -767,59 +774,14 @@ export class Generator {
for (let dep of preloadDeps.sort()) {
if (first || whitespace) preloads += newlineTab;
if (first) first = false;
if (integrity) {
preloads += `<link rel="modulepreload" href="${rootUrl || htmlUrl
? relativeUrl(
new URL(dep),
new URL(rootUrl || htmlUrl),
!!rootUrl
)
: dep
}" integrity="${await getIntegrity(
dep,
this.traceMap.resolver.fetchOpts
)}" />`;
} else {
preloads += `<link rel="modulepreload" href="${rootUrl || htmlUrl
? relativeUrl(
new URL(dep),
new URL(rootUrl || htmlUrl),
!!rootUrl
)
: dep
}" />`;
}
}
}

// when applying integrity, all existing script tags have their integrity updated
if (integrity) {
for (const module of analysis.modules) {
if (!module.attrs.src) continue;
if (module.attrs.integrity) {
replacer.remove(
module.attrs.integrity.start -
(replacer.source[
replacer.idx(module.attrs.integrity.start - 1)
] === " "
? 1
: 0),
module.attrs.integrity.end + 1
);
}
const lastAttr = Object.keys(module.attrs)
.filter((attr) => attr !== "integrity")
.sort((a, b) =>
module.attrs[a].end > module.attrs[b].end ? -1 : 1
)[0];
replacer.replace(
module.attrs[lastAttr].end + 1,
module.attrs[lastAttr].end + 1,
` integrity="${await getIntegrity(
resolveUrl(module.attrs.src.value, this.mapUrl, this.rootUrl),
this.traceMap.resolver.fetchOpts
)}"`
);
preloads += `<link rel="modulepreload" href="${rootUrl || htmlUrl
? relativeUrl(
new URL(dep),
new URL(rootUrl || htmlUrl),
!!rootUrl
)
: dep
}" />`;
}
}

Expand Down Expand Up @@ -930,7 +892,7 @@ export class Generator {
if (Array.isArray(install)) {
if (install.length === 0) {
const { map, staticDeps, dynamicDeps } =
await this.traceMap.finishInstall();
await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
return { staticDeps, dynamicDeps };
}
Expand Down Expand Up @@ -1011,7 +973,7 @@ export class Generator {
} finally {
if (--this.installCnt === 0) {
const { map, staticDeps, dynamicDeps } =
await this.traceMap.finishInstall();
await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
if (!error) return { staticDeps, dynamicDeps };
}
Expand All @@ -1027,7 +989,7 @@ export class Generator {
await this.traceMap.processInputMap;
if (--this.installCnt === 0) {
const { map, staticDeps, dynamicDeps } =
await this.traceMap.finishInstall();
await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
return { staticDeps, dynamicDeps };
}
Expand Down Expand Up @@ -1103,7 +1065,7 @@ export class Generator {
await this._install(installs, mode);
if (--this.installCnt === 0) {
const { map, staticDeps, dynamicDeps } =
await this.traceMap.finishInstall();
await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
return { staticDeps, dynamicDeps };
}
Expand Down Expand Up @@ -1135,7 +1097,7 @@ export class Generator {
}
this.traceMap.pins = pins;
if (--this.installCnt === 0) {
const { staticDeps, dynamicDeps, map } = await this.traceMap.finishInstall();
const { staticDeps, dynamicDeps, map } = await this.traceMap.finishInstall(this.traceMap.pins, this.integrity);
this.map = map;
return { staticDeps, dynamicDeps };
}
Expand All @@ -1144,19 +1106,22 @@ export class Generator {
async extractMap(
pins: string | string[],
mapUrl?: URL | string,
rootUrl?: URL | string | null
rootUrl?: URL | string | null,
integrity?: boolean
) {
if (typeof mapUrl === "string") mapUrl = new URL(mapUrl, this.baseUrl);
if (typeof rootUrl === "string") rootUrl = new URL(rootUrl, this.baseUrl);
if (!Array.isArray(pins)) pins = [pins];
if (typeof integrity !== 'boolean') integrity = this.integrity;
if (this.installCnt++ !== 0)
throw new JspmError(`Cannot run extract map during installs`);
this.traceMap.startInstall();
await this.traceMap.processInputMap;
if (--this.installCnt !== 0)
throw new JspmError(`Another install was started during extract map.`);
const { map, staticDeps, dynamicDeps } = await this.traceMap.finishInstall(
pins
pins,
integrity
);
map.rebase(mapUrl, rootUrl);
map.flatten();
Expand Down
7 changes: 5 additions & 2 deletions src/trace/analysis.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getIntegrity } from "../common/integrity.js";

export interface Analysis {
deps: string[];
dynamicDeps: string[];
Expand All @@ -7,6 +9,7 @@ export interface Analysis {

// for commonjs format, true iff the module uses a CJS-only global
usesCjs?: boolean;
integrity: `sha384-${string}`;
}

export { createTsAnalysis } from "./ts.js";
Expand Down Expand Up @@ -43,7 +46,7 @@ export function createEsmAnalysis(
}
}
const size = source.length;
return { deps, dynamicDeps, cjsLazyDeps: null, size, format: "esm" };
return { deps, dynamicDeps, cjsLazyDeps: null, size, format: "esm", integrity: getIntegrity(source) };
}

const registerRegEx =
Expand Down Expand Up @@ -78,5 +81,5 @@ export function createSystemAnalysis(
}
}
const size = source.length;
return { deps, dynamicDeps, cjsLazyDeps: null, size, format: "system" };
return { deps, dynamicDeps, cjsLazyDeps: null, size, format: "system", integrity: getIntegrity(source) };
}
4 changes: 3 additions & 1 deletion src/trace/cjs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Analysis } from "./analysis";
import { getIntegrity } from "../common/integrity.js";
import { Analysis } from "./analysis.js";

// See: https://nodejs.org/docs/latest/api/modules.html#the-module-scope
const cjsGlobals: string[] = [
Expand Down Expand Up @@ -109,6 +110,7 @@ export async function createCjsAnalysis(
size: source.length,
format: "commonjs",
usesCjs,
integrity: getIntegrity(source)
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/trace/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from "./analysis.js";
import { Installer, PackageProvider } from "../install/installer.js";
import { SemverRange } from "sver";
import { getIntegrity } from "../common/integrity.js";

let realpath, pathToFileURL;

Expand Down Expand Up @@ -889,6 +890,7 @@ export class Resolver {
cjsLazyDeps: null,
size: source.byteLength,
format: "wasm",
integrity: getIntegrity(new Uint8Array(source))
};
}

Expand All @@ -911,6 +913,7 @@ export class Resolver {
cjsLazyDeps: null,
size: source.length,
format: "json",
integrity: getIntegrity(source)
};
} catch {}
}
Expand All @@ -923,6 +926,7 @@ export class Resolver {
cjsLazyDeps: null,
size: source.length,
format: "css",
integrity: getIntegrity(source)
};
} catch {}
}
Expand Down
Loading
Loading