a post-component library for building user-interfaces on the web.
at push-qkwusxtxonpq 105 lines 2.9 kB view raw
1import { serve } from '@hono/node-server' 2import { serveStatic } from '@hono/node-server/serve-static' 3import { transformSync } from 'amaro' 4import { Hono } from 'hono' 5import * as puppeteer from 'puppeteer' 6import type { Runtime } from './main.ts' 7 8export async function create_browser_runtime(): Promise<Runtime> { 9 const browser = await puppeteer.launch({ 10 // headless: false, 11 // devtools: true, 12 }) 13 14 const app = new Hono() 15 16 app.get('/@runner', c => 17 c.html(` 18 <!doctype html> 19 <link rel="icon" href="data:" /> 20 <script type="importmap">${JSON.stringify({ 21 imports: { 22 dhtml: '/dist/index.js', 23 'dhtml/client': '/dist/client.js', 24 'dhtml/server': '/dist/server.js', 25 birpc: '/node_modules/birpc/dist/index.mjs', 26 devalue: '/node_modules/devalue/index.js', 27 mitata: '/node_modules/mitata/src/main.mjs', 28 }, 29 })}</script> 30 <script type="module" src="/scripts/test/runtime.ts"></script> 31 `), 32 ) 33 34 app.use(async (c, next) => { 35 await next() 36 if (c.res.ok && c.req.path.endsWith('.ts')) { 37 const { code } = transformSync(await c.res.text(), { mode: 'strip-only' }) 38 c.res = c.body(code) 39 c.res.headers.set('content-type', 'text/javascript') 40 c.res.headers.delete('content-length') 41 } 42 }) 43 app.use(serveStatic({ root: './' })) 44 app.use(async (c, next) => { 45 await next() 46 c.header('Cross-Origin-Opener-Policy', 'same-origin') 47 c.header('Cross-Origin-Embedder-Policy', 'require-corp') 48 c.header('Cross-Origin-Resource-Policy', 'same-origin') 49 }) 50 51 const server = serve({ 52 fetch: app.fetch, 53 port: 0, 54 }) 55 56 let addr = server.address()! 57 if (typeof addr !== 'string') { 58 addr = addr.family === 'IPv6' ? `[${addr.address}]:${addr.port}` : `${addr.address}:${addr.port}` 59 } 60 61 const [page] = await browser.pages() 62 page.on('console', async msg => { 63 const args = await Promise.all(msg.args().map(arg => arg.jsonValue())) 64 const type = msg.type() 65 switch (type) { 66 case 'startGroup': 67 console.group(...args) 68 break 69 case 'startGroupCollapsed': 70 console.groupCollapsed(...args) 71 break 72 case 'endGroup': 73 console.groupEnd() 74 break 75 case 'verbose': 76 console.log(...args) 77 break 78 default: 79 const fn = console[type] 80 // @ts-expect-error 81 fn(...args) 82 } 83 }) 84 const { port1, port2 } = new MessageChannel() 85 await page.exposeFunction('__postMessage', (data: any) => port1.postMessage(data)) 86 87 await page.coverage.startJSCoverage({ includeRawScriptCoverage: true }) 88 await page.goto(`http://${addr}/@runner`) 89 90 const onmessage = await page.waitForFunction(() => window.__onmessage) 91 port1.onmessage = e => onmessage.evaluate((fn, data) => fn(data), e.data) 92 93 return { 94 port: port2, 95 async coverage() { 96 const coverage = await page.coverage.stopJSCoverage() 97 return coverage.map(c => c.rawScriptCoverage!) 98 }, 99 async [Symbol.asyncDispose]() { 100 port1.close() 101 server.close() 102 await browser.close() 103 }, 104 } 105}