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