diff --git a/.changeset/rude-pets-hope.md b/.changeset/rude-pets-hope.md
new file mode 100644
index 0000000..66aa96a
--- /dev/null
+++ b/.changeset/rude-pets-hope.md
@@ -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
+///
+
+window.hi('Bob');
+```
diff --git a/src/Processor.ts b/src/Processor.ts
index e07089f..ae98327 100644
--- a/src/Processor.ts
+++ b/src/Processor.ts
@@ -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() {
@@ -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,
diff --git a/src/config.ts b/src/config.ts
index c37bf80..a356da5 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -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,
@@ -16,26 +17,59 @@ export type Config = {
playwright?: (page: PlaywrightConfig) => Promise
}
-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 {
+ 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> {
+ // 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 {
- const rootDir = process.cwd();
return {
- ...await importConfig(rootDir),
+ ...await importConfig(),
}
}
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..b061c88
--- /dev/null
+++ b/src/utils.ts
@@ -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);
+}