Skip to content

Commit

Permalink
feat: support ts config files (#55)
Browse files Browse the repository at this point in the history
* feat: support ts config files

* chore: changeset
  • Loading branch information
heyMP authored Aug 8, 2024
1 parent 8cf0415 commit 708f25e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 18 deletions.
34 changes: 34 additions & 0 deletions .changeset/rude-pets-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"@heymp/scratchpad": minor
---

Support ts config files

`scratchpad.config.ts`
```ts
import { Config } from '@heymp/scratchpad/src/config.js';

export function hi(name: string) {
console.log(`Hi there ${name}`);
}

declare global {
interface Window {
hi: typeof hi;
}
}

export default ({
playwright: async (args) => {
const { context } = args;
await context.exposeFunction('hi', hi);
}
}) satisfies Config;
```

`test.ts`
```.ts
/// <reference path="./scratchpad.config.ts" />

window.hi('Bob');
```
4 changes: 2 additions & 2 deletions src/Processor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import { join } from 'node:path';
import * as esbuild from 'esbuild';
import { build } from 'esbuild';

export class ProcessorChangeEvent extends Event {
constructor() {
Expand Down Expand Up @@ -58,7 +58,7 @@ export class Processor extends EventTarget {
throw new Error(`${file} file not found.`);
}
if (file.endsWith('.ts')) {
const { outputFiles: [stdout]} = await esbuild.build({
const { outputFiles: [stdout]} = await build({
entryPoints: [file],
format: 'esm',
bundle: true,
Expand Down
66 changes: 50 additions & 16 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { join } from 'node:path';
import { stat } from 'node:fs/promises';
import type { Page, BrowserContext, Browser } from 'playwright';
import { build } from 'esbuild';
import { exists, esm } from './utils.js';

export type PlaywrightConfig = {
page: Page,
Expand All @@ -16,26 +17,59 @@ export type Config = {
playwright?: (page: PlaywrightConfig) => Promise<void>
}

const exists = (path:string) => stat(path).then(() => true, () => false);

async function importConfig(rootDir:string) {
const path = join(rootDir, './scratchpad.config.js');
if (await exists(path)) {
return import(path)
.then(x => x.default)
.catch(e => {
console.error(e);
return {};
});
} else {
return {};
/**
* Looks at the users root path to see if there
* are any valid config files.
* @returns either the absolute path to the valid config
* or returns false if none have config
* has not been found.
*/
async function getConfigPath(): Promise<string | false> {
if (await exists(join(process.cwd(), 'scratchpad.config.js'))) {
return join(process.cwd(), 'scratchpad.config.js');
}
if (await exists(join(process.cwd(), 'scratchpad.config.ts'))) {
return join(process.cwd(), 'scratchpad.config.ts');
}
return false;
}

/**
* Get the import config object from the config file.
*/
async function importConfig(): Promise<Partial<Config>> {
// determine if there are any valid configs
const configPath = await getConfigPath();

// if there is no config path then return
// an empty object
if (!configPath) {
return {}
}

try {
// use esbuild to load the .js or .ts file
const { outputFiles: [stdout] } = await build({
entryPoints: [configPath],
format: 'esm',
bundle: true,
write: false,
});
const contents = new TextDecoder().decode(stdout.contents);
const module = await import(esm`${contents}`);
return module.default ?? {};
} catch (e) {
console.error(`An error occured parsing config file.${configPath}`);
console.error(``);
console.error(`Path: ${configPath}`);
console.error(``);
throw e;
}
}

export async function getConfig(): Promise<Config> {
const rootDir = process.cwd();
return {
...await importConfig(rootDir),
...await importConfig(),
}
}

23 changes: 23 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { stat } from 'node:fs/promises';

/**
* Helper function to check if a file exists
*/
export const exists = (path: string) => stat(path).then(() => true, () => false);

/**
* Template Literal function that converts an string
* containing ESM javascript to data URI.
*
* @example
* const m1 = esm`export function f() { return 'Hello!' }`;
* const m2 = esm`import {f} from '${m1}'; export default f()+f();`;
* import(m1)
*/
export function esm(templateStrings: TemplateStringsArray, ...substitutions: any[]): string {
let js = templateStrings.raw[0];
for (let i = 0; i < substitutions.length; i++) {
js += substitutions[i] + templateStrings.raw[i + 1];
}
return 'data:text/javascript;base64,' + btoa(js);
}

0 comments on commit 708f25e

Please sign in to comment.