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(feature-detector): introduces findGlobals() utility #234

Merged
merged 10 commits into from
Jan 18, 2023
5 changes: 5 additions & 0 deletions .changeset/light-gifts-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@edge-runtime/feature-detector': minor
---

Introduces findDependencies() utility
22 changes: 19 additions & 3 deletions docs/pages/packages/feature-detector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,38 @@ This package includes built-in TypeScript support.

## Usage

Here is an example of checking whether a file's default export is a function matching the edge signature, such as:
Here is a test file. It's in JavaScript, but TypeScript is supported as well.

```js
// test.js
if (typeof process !== 'undefined') {
console.log('in node', __dirname)
} else if (typeof window !== 'undefined') {
console.log('in a browser', document)
}
setTimeout(() => console.log('setTimeout is not a global, nor console'), 0)

export default function () {
return Response.json({ message: 'hello world!' })
}
```

In the code snippet bellow, we're checking:

1. whether this file is a function matching the edge signature
2. globals used, that are not provided by the Edge Runtime

```ts
import { hasEdgeSignature } from '@edge-runtime/feature-detector'
import { hasEdgeSignature, findGlobals } from '@edge-runtime/feature-detector'

const sourceFilePath = './test.js' // could be TypeScript as well. Must be in current working directory

// 1
if (hasEdgeSignature(sourceFilePath)) {
console.log(`${sourcefilePath} can run on the edge``)
console.log(`${sourcefilePath} can run on the edge`)
}
// 2
console.log(findGlobals(sourceFilePath))
feugy marked this conversation as resolved.
Show resolved Hide resolved
```

## API
Expand Down
5 changes: 4 additions & 1 deletion packages/feature-detector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@
"ts-morph": "17.0.1"
},
"devDependencies": {
"axios": "0.27.2",
feugy marked this conversation as resolved.
Show resolved Hide resolved
"jest-text-transformer": "1.0.4",
"next": "13",
"react": "18",
"react-dom": "18",
"tsup": "6"
},
"peerDependencies": {
Kikobeats marked this conversation as resolved.
Show resolved Hide resolved
"esbuild": "^0.16.16"
},
"engines": {
"node": ">=14"
},
Expand All @@ -42,7 +46,6 @@
"scripts": {
"build": "tsup",
"clean:build": "rm -rf dist",
"patch-type-definition": "ts-node scripts/patch-type-definition.ts",
"prebuild": "pnpm run clean:build",
"test": "jest"
},
Expand Down
31 changes: 31 additions & 0 deletions packages/feature-detector/scripts/build-fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// run with ts-node
import { build } from 'esbuild'
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'

const fixtureFolder = join(__dirname, '../test/fixtures')
const resultExtension = '.out.js'

async function transpileAll() {
for (const file of await readdir(fixtureFolder)) {
if (!file.endsWith(resultExtension)) {
await transpileFile(file)
}
}
}

async function transpileFile(file: string) {
const filePath = join(fixtureFolder, file)
await build({
entryPoints: [filePath],
bundle: true,
outfile: filePath.replace(/\..+$/, resultExtension),
})
}

transpileAll()
.then(() => process.exit(0))
.catch((error) => {
console.log('Errored', error)
process.exit(1)
})
107 changes: 0 additions & 107 deletions packages/feature-detector/scripts/patch-type-definition.ts

This file was deleted.

52 changes: 52 additions & 0 deletions packages/feature-detector/src/find-globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { dirname, resolve } from 'path'
import { Project, SourceFile, ts } from 'ts-morph'
import { buildProject } from './utils/project'

/**
* Find the list of globals used by source files in the provided project.
* Analyzed source files can be filtered by provided a list of glob patterns (default to all TypeScript and JavaScript files, excluding type definitions)
*/
export function findGlobals(
sourcePath: string,
project: Project = buildProject()
): string[] {
const globals = new Set<string>()
const sourceFile = project.getSourceFileOrThrow(sourcePath)
addFileGlobals(sourceFile, globals)
for (const imported of sourceFile.getImportDeclarations()) {
console.log(imported.getModuleSpecifierSourceFileOrThrow())
const importedFile = project.getSourceFile(
feugy marked this conversation as resolved.
Show resolved Hide resolved
feugy marked this conversation as resolved.
Show resolved Hide resolved
resolve(
dirname(sourceFile.getFilePath()),
imported.getModuleSpecifierValue()
)
)
feugy marked this conversation as resolved.
Show resolved Hide resolved
if (importedFile) {
addFileGlobals(importedFile, globals)
}
}
return [...globals]
}

function addFileGlobals(sourceFile: SourceFile, globals: Set<string>) {
const program = sourceFile.getProject().getProgram().compilerObject
const diagnostics = program.getSemanticDiagnostics(sourceFile.compilerNode)
feugy marked this conversation as resolved.
Show resolved Hide resolved
for (const { code, messageText } of diagnostics) {
if (
feugy marked this conversation as resolved.
Show resolved Hide resolved
// see all messages: https://github.com/microsoft/TypeScript/blob/main/src/compiler/diagnosticMessages.json#L1631
code === 2304 ||
code === 2311 ||
code === 2552 ||
(code >= 2562 && code <= 2563) ||
(code >= 2580 && code <= 2584) ||
(code >= 2591 && code <= 2593)
) {
const match = ts
.flattenDiagnosticMessageText(messageText, '\n')
.match(/^Cannot find name '([^']+)'\./)
if (match) {
globals.add(match[1])
}
}
}
}
1 change: 1 addition & 0 deletions packages/feature-detector/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './find-globals'
export * from './has-edge-signature'
4 changes: 3 additions & 1 deletion packages/feature-detector/src/utils/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import typeDefinitionContent from './type-definition.txt'
export function buildProject() {
const project = new Project({
compilerOptions: {
types: [], // does not load node.js types to only rely on types provided by Edge runtime
allowJs: true,
checkJs: true,
},
})
project.createSourceFile('node_modules/index.d.ts', typeDefinitionContent)
project.addDirectoryAtPathIfExists('.')
project.addDirectoryAtPath('.')
return project
}
Loading