2 changed files with 172 additions and 0 deletions
@ -0,0 +1,143 @@ |
|||||
|
function log(message, cls = 'cyan') { |
||||
|
const el = document.createElement('pre') |
||||
|
el.classList.add(cls) |
||||
|
el.innerText = message |
||||
|
document.body.append(el) |
||||
|
} |
||||
|
|
||||
|
class App { |
||||
|
jApiBaseUrl = 'https://data.jsdelivr.com/v1/' |
||||
|
jCdnBaseUrl = 'https://cdn.jsdelivr.net/npm/' |
||||
|
|
||||
|
scripts = { |
||||
|
'@rollup/browser': { |
||||
|
version: '3.20.4', |
||||
|
path: 'dist/rollup.browser.js', |
||||
|
sha: 'sha256-GgOznxZmgghx1a7CH09B+VmDKtziPO5tAnC5gC+/5Kw=', |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
topLevelDeps = [ |
||||
|
"@codemirror/autocomplete", |
||||
|
"@codemirror/commands", |
||||
|
"@codemirror/language", |
||||
|
"@codemirror/lint", |
||||
|
"@codemirror/search", |
||||
|
"@codemirror/state", |
||||
|
"@codemirror/view", |
||||
|
"@codemirror/lang-javascript", |
||||
|
] |
||||
|
|
||||
|
constructor() { |
||||
|
this.downloads = [] |
||||
|
this.run().catch(e => { |
||||
|
log(`${e}`) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
checkOk(resp) { |
||||
|
if (!resp.ok) { |
||||
|
throw new Error(`HTTP request failed: ${resp.status}`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async loadDep(dep) { |
||||
|
this.scripts[dep] = {} |
||||
|
const dataResp = await fetch( |
||||
|
this.jApiBaseUrl + |
||||
|
`packages/npm/${dep}/resolved`, |
||||
|
{ |
||||
|
headers: { |
||||
|
'User-Agent': |
||||
|
'https://codeberg.org/macchiato', |
||||
|
}, |
||||
|
}, |
||||
|
) |
||||
|
this.checkOk(dataResp) |
||||
|
const {version} = await dataResp.json() |
||||
|
this.scripts[dep].version = version |
||||
|
const pkgResp = await fetch( |
||||
|
this.jCdnBaseUrl + |
||||
|
`${dep}@${version}/package.json` |
||||
|
) |
||||
|
this.checkOk(pkgResp) |
||||
|
log(dep) |
||||
|
const pkg = await pkgResp.json() |
||||
|
this.scripts[dep].path = ( |
||||
|
pkg.module ?? pkg.main |
||||
|
) |
||||
|
this.downloads.push(this.getScript(dep)) |
||||
|
const deps = Object.keys( |
||||
|
pkg.dependencies || {} |
||||
|
).filter(dep => !(dep in this.scripts)) |
||||
|
await Promise.allSettled(deps.map(dep => ( |
||||
|
this.loadDep(dep) |
||||
|
))) |
||||
|
} |
||||
|
|
||||
|
async checkIntegrity(resp, name, script) { |
||||
|
const blob = await resp.blob() |
||||
|
const ab = await blob.arrayBuffer() |
||||
|
const hash = await crypto.subtle.digest( |
||||
|
"SHA-256", ab |
||||
|
) |
||||
|
const checkValue = 'sha256-' + btoa( |
||||
|
String.fromCharCode( |
||||
|
...new Uint8Array(hash) |
||||
|
) |
||||
|
) |
||||
|
if (checkValue !== script.sha) { |
||||
|
throw new Error( |
||||
|
'failed integrity check: ' + |
||||
|
`${checkValue} !== ${script.sha}` |
||||
|
) |
||||
|
} |
||||
|
return ab |
||||
|
} |
||||
|
|
||||
|
async getScript(name) { |
||||
|
log('[downloading] ' + name, 'green') |
||||
|
const script = this.scripts[name] |
||||
|
if (script.text) { |
||||
|
return script.text |
||||
|
} |
||||
|
const url = ( |
||||
|
this.jCdnBaseUrl + |
||||
|
`${name}@${script.version}/${script.path}` |
||||
|
) |
||||
|
const resp = await fetch(url) |
||||
|
this.checkOk(resp) |
||||
|
if (script.sha) { |
||||
|
const ab = await this.checkIntegrity( |
||||
|
resp, name, script |
||||
|
) |
||||
|
script.text = new TextDecoder().decode(ab) |
||||
|
} else { |
||||
|
script.text = await resp.text |
||||
|
script.sha = resp.integrity |
||||
|
} |
||||
|
log('[downloaded] ' + url, 'green') |
||||
|
return script.text |
||||
|
} |
||||
|
|
||||
|
async loadScript(name) { |
||||
|
const text = await this.getScript(name) |
||||
|
const s = document.createElement('script') |
||||
|
s.text = text |
||||
|
document.head.append(s) |
||||
|
} |
||||
|
|
||||
|
async run() { |
||||
|
await Promise.allSettled( |
||||
|
this.topLevelDeps.map(dep => ( |
||||
|
this.loadDep(dep) |
||||
|
)) |
||||
|
) |
||||
|
await Promise.allSettled(this.downloads) |
||||
|
//await this.loadScript('@rollup/browser')
|
||||
|
//const { rollup } = window.rollup
|
||||
|
//output.value = `${typeof rollup}`
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
new App() |
||||
@ -0,0 +1,29 @@ |
|||||
|
html, body { |
||||
|
min-height: 100%; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: stretch; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
background: navy; |
||||
|
margin: 10px; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
pre { |
||||
|
padding: 8px; |
||||
|
border-radius: 5px; |
||||
|
margin: 0; |
||||
|
overflow-x: auto; |
||||
|
} |
||||
|
|
||||
|
pre.cyan { |
||||
|
background: cyan; |
||||
|
} |
||||
|
|
||||
|
pre.green { |
||||
|
background: lightgreen; |
||||
|
} |
||||
Loading…
Reference in new issue