Open nandorojo opened 2 months ago
I can't seem to get this working. However, I'll share the solution I made myself which has been working for me. Definitely open to feedback there though.
extensions: [..., tsAta({ env })]
import { EditorView } from '@codemirror/view'
import { tsFacet } from '@valtown/codemirror-ts'
import {
setupTypeAcquisition,
// @ts-ignore
getReferencesForModule,
} from '@typescript/ata'
import typescript from 'typescript'
import type { VirtualTypeScriptEnvironment } from '@typescript/vfs'
import { createOrUpdateFile } from 'app/features/code-editor/typescript/vendor/sync/update'
import { type VirtualTypeScriptEnvironment } from '@typescript/vfs'
const getReferences =
getReferencesForModule as typeof import('@typescript/ata/src/index').getReferencesForModule
/**
* Get dependencies for the TypeScript environment
*/
export function tsAta({ env }: { env: VirtualTypeScriptEnvironment }) {
console.log('[ata][ts]')
const ata = setupTypeAcquisition({
typescript,
projectName: 'typescript-ata',
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
// Add code to your runtime at the path...
createOrUpdateFile(env, path, code)
}
},
})
let first = true
return EditorView.updateListener.of((update) => {
const config = update.view.state.facet(tsFacet)
if (!config) return
if (!update.docChanged && !first) return
first = false
let content = update.state.doc.toString() || ' '
getReferences(typescript, content).forEach(({ module }) => {
if (module.startsWith('npm:')) {
// create a declaration file from npm:package → package
const [name, version] = module.slice(4).split('@')
content = content.replaceAll(`"${module}"`, `'${module}'`) // single quotes
content = content.replaceAll(
`'${module}'`,
// the new line is important to not break require statements etc with the comment
`'${name}' // types: ${version ?? 'latest'}\n`
)
createOrUpdateFile(
env,
`node_modules/${name}/__custom-deno-types.d.ts`,
`
declare module '${module}' {
export * from '${name}'
}
`
)
}
})
ata(content)
})
}
export function createOrUpdateFile(
env: VirtualTypeScriptEnvironment,
path: string,
code: string
): void {
if (!env.getSourceFile(path)) {
env.createFile(path, code)
} else {
env.updateFile(path, code)
}
}
That seems brilliant! I'll tinker with something similar.
Just to add to the discussion here:
The solution I posted above has been working. However, using the TS ATA directly in the browser has some downsides I'm finding:
I wonder if there could be a way to run it via a middleware server or something that could:
.d.ts
file(s) that the web browser will use. If possible bundle it into one file. Maybe this could just use tsc
or a faster equivalent?.d.ts
file to the browser, and cache it for future requestsFor example, you could call your server with something like request('/ts-ata', { code })
and it returns the .d.ts
file(s) to be used by the language server on the browser.
Have you guys experimented with something like this @tmcw?
Some downsides to the above approach:
ata
provides a listener for each file that comes in as it streams in, which is useful for getting the types of some packages while others are loading. So for the first time, this would have a trade-off.Potential Solutions:
One clear issue with this approach is that, every time someone types, you need a new cache key. However, this can be easily solved by using a hash of the result of getReferencesForModule
as the cache key. We only need to cache things based on which modules are in a file so that should do the trick, I think.
Lastly, one simple thing I did to make this much faster was to only call ata(content)
if the result of getReferences
was greater than 0.
(Apologies for the many Sunday morning messages)
Tried the example app using
npm i && npm run dev
, but it doesn't pick up the types. I'll see if I can find why. My guess is it's not updating the TS env accordingly, because the network tab does indeed show the files fetching.