Skip to content

bigmistqke/repl

Repository files navigation

@bigmistqke/repl

@bigmistqke/repl

@bigmistqke/repl provides utilities to compose online code editors and REPLs. It manages file transformations and generates executable URLs for browser-based code execution.

Table of Contents

Quick Start

Create a simple code playground with custom extensions:

import { createFileUrlSystem, transformModulePaths, PathUtils, type Extension } from '@bigmistqke/repl'
import ts from 'typescript'

// Custom JavaScript extension with module resolution
const jsExtension = {
  type: 'javascript',
  transform: ({ source, path, fileUrls }) => {
    return transformModulePaths({
      ts,
      source,
      transform: (modulePath) => {
        if (modulePath.startsWith('.')) {
          return fileUrls.get(PathUtils.resolvePath(path, modulePath))
        }
        return `https://esm.sh/${modulePath}`
      }
    })
  }
} satisfies Extension

// Custom TypeScript extension
const tsExtension = {
  type: 'javascript', 
  transform: ({ source, path, fileUrls }) => {
    // Transpile TypeScript first
    const jsSource = ts.transpile(source)
    // Then transform modules
    return jsExtension.transform({ source: jsSource, path, fileUrls })
  }
} satisfies Extension

// Create file URL system
const fileUrls = createFileUrlSystem({
  readFile: (path) => files[path],
  extensions: {
    js: jsExtension,
    ts: tsExtension,
    css: { type: 'css' },
    html: { type: 'html' }
  }
})

// File contents
const files = {
  '/index.html': '<script type="module" src="./main.ts"></script>',
  '/main.ts': 'console.log("Hello from TypeScript!")',
  '/style.css': 'body { background: blue; }'
}

// Get executable URL
const url = fileUrls.get('/index.html')

// Use in iframe
return <iframe src={url} />

For convenience, you can also use pre-built utilities:

import { defaultFileUrlSystem } from '@bigmistqke/repl'

// Pre-configured with JS/TS/HTML/CSS support
const fileUrls = defaultFileUrlSystem({
  readFile: (path) => files[path],
  ts // TypeScript compiler
})

createFileUrlSystem

Creates a system for managing file URLs with automatic lifecycle management:

import { createFileUrlSystem } from '@bigmistqke/repl'

const fileUrls = createFileUrlSystem({
  readFile: (path) => myFileSystem.read(path),
  extensions: {
    css: { type: 'css' },
    scss: sassExtension,
    js: bundlerExtension,
    // ... more extensions
  }
})

// Get URL for a file (automatically managed)
const url = fileUrls.get('/src/main.js')

// Invalidate cached URL when file changes
fileUrls.invalidate('/src/main.js')

defaultFileUrlSystem

Pre-configured file URL system with common file types:

import { defaultFileUrlSystem } from '@bigmistqke/repl'

// Pre-configured with JS/TS/HTML/CSS support
const fileUrls = defaultFileUrlSystem({
  readFile: (path) => files[path],
  ts // TypeScript compiler
})

Extensions

Extensions are simple objects that define how files are transformed. The core interface is:

interface Extension {
  type: FileType // 'javascript' | 'css' | 'html' | 'wasm' | 'plain'
  transform?: (config: TransformConfig) => string | Accessor<string>
}

interface TransformConfig {
  path: string        // File path being transformed
  source: string      // File content
  fileUrls: FileUrlSystem // Access to other file URLs
}

Simple extension examples:

// CSS extension (no transformation needed)
const cssExtension = {
  type: 'css'
}

// Custom Sass extension
const sassExtension = {
  type: 'css',
  transform: ({ source }) => {
    return sass.renderSync({ data: source }).css.toString()
  }
}

// Custom JavaScript bundler
const bundlerExtension = {
  type: 'javascript',
  transform: ({ source, path, fileUrls }) => {
    // Transform imports to use file URLs
    return transformModulePaths(source, (modulePath) => {
      if (modulePath.startsWith('.')) {
        return fileUrls.get(resolvePath(path, modulePath))
      }
      return `https://esm.sh/${modulePath}`
    })
  }
}

Convenience Utilities

For common use cases, convenience utilities are provided:

import { createJSExtension } from '@bigmistqke/repl'

// JavaScript extension with TypeScript support
const jsExtension = createJSExtension({
  ts, // TypeScript compiler
  readFile: fs.readFile,
  transpile: false, // Don't transpile by default
  transform: ({ source, path }) => {
    // Custom transformation
    return transformedSource
  }
})

// Extend to create a TypeScript variant
const tsExtension = jsExtension.extend({ 
  transpile: true // Enable transpilation
})

Utilities

transformModulePaths

Transform import/export declarations in JavaScript/TypeScript:

const transformed = transformModulePaths({
  ts,
  source,
  transform: (modulePath) => {
    if (modulePath.startsWith('.')) {
      return fileUrls.get(PathUtils.resolvePath(currentPath, modulePath))
    }
    return `https://esm.sh/${modulePath}`
  }
})

getModulePathRanges

Extract all module path ranges from TypeScript source code. This utility finds import and export declarations and returns their module specifier positions:

import { getModulePathRanges } from '@bigmistqke/repl'

const ranges = getModulePathRanges({
  ts,
  source: `
    import { foo } from "./bar.js"
    export { baz } from "../qux.ts"
  `
})

// Returns:
// [
//   { start: 21, end: 30, path: "./bar.js", isImport: true },
//   { start: 53, end: 63, path: "../qux.ts", isImport: false }
// ]

This is useful for analyzing module dependencies, building custom transformations, or creating tooling that needs to understand module structure.

getModuleDependencies

Analyze TypeScript/JavaScript modules and their dependencies to extract all local files and external packages. This function recursively walks through module dependency trees and returns both local files (with their content) and external dependencies (package names and URLs):

import { getModuleDependencies } from '@bigmistqke/repl'
import * as ts from 'typescript'

// Single entry point
const result = await getModuleDependencies({
  entry: 'src/index.ts',
  readFile: async (path) => await fs.readFile(path, 'utf-8'),
  ts
})

// Multiple entry points
const result = await getModuleDependencies({
  entry: ['src/main.ts', 'src/worker.ts', 'src/polyfills.ts'],
  readFile: async (path) => await fs.readFile(path, 'utf-8'),
  ts
})

console.log('Local files:', Object.keys(result.local))
// => ['src/main.ts', 'src/worker.ts', 'src/polyfills.ts', 'src/shared/utils.ts', ...]

console.log('External packages:', result.external)
// => ['react', 'lodash', '@mui/material', 'axios']

Selective analysis with include options:

// Only analyze static imports, exclude exports and dynamic imports
const result = await getModuleDependencies({
  entry: 'app.ts',
  readFile,
  ts,
  include: { 
    imports: true, 
    exports: false, 
    dynamicImports: false 
  }
})

Return structure:

interface ModuleDependencies {
  local: Record<string, string>  // File paths mapped to their content
  external: string[]             // External package names and URLs
}

Use cases:

  • Bundle analyzers: Understand what gets included in your bundle
  • Dependency visualization: Map out your project's dependency graph
  • Code splitting: Identify which files to group together
  • Multi-entry bundlers: Analyze dependencies across multiple entry points (main app, web workers, polyfills)
  • Package extraction: Find all external dependencies for package.json generation
  • Static analysis: Analyze codebases without executing them
  • Monorepo tooling: Analyze shared dependencies between packages

This is particularly useful for building development tools, bundlers, or any system that needs to understand module relationships and dependencies.

transformHtml

Transform HTML content with a jQuery-like API:

transformHtml({
  readFile,
  path: '/index.html',
  source: htmlContent
})
  // Transform all script contents
  .transformScriptContent(({ source }) => transformJs(source))
  // Bind relative src attributes to file URLs
  .bindScriptSrc()
  // Bind relative href attributes to file URLs
  .bindLinkHref()
  .toString()

transformHtmlWorker

Worker-friendly HTML transformation for environments without DOM access:

import { transformHtmlWorker } from '@bigmistqke/repl'

// Worker-friendly HTML transformation
const transformed = transformHtmlWorker({ 
  source, 
  path, 
  fileUrls,
  transformModule: jsTransform
})

babelTransform

Babel transformation support:

import { babelTransform } from '@bigmistqke/repl'
import * as babel from '@babel/standalone'

const transform = await babelTransform({
  babel,
  presets: ['react'],
  plugins: []
})

const result = transform(source, path)

createFileUrl

Creates an object URL from a text source:

import { createFileUrl } from '@bigmistqke/repl'

// Create a URL for HTML content
const url = createFileUrl('<div>Hello World</div>', 'html')

// Create a URL for JavaScript
const jsUrl = createFileUrl('console.log("Hello")', 'javascript')

// Use in iframe
const iframe = document.createElement('iframe')
iframe.src = url

downloadTypesFromUrl

Download TypeScript definitions from a URL:

import { downloadTypesFromUrl } from '@bigmistqke/repl'

const types = await downloadTypesFromUrl({
  ts,
  url: 'https://esm.sh/react@18/index.d.ts',
  cdn: 'https://esm.sh'
})
// Returns: Record<string, string>

Download Types From Package

Download TypeScript definitions from npm packages. Relies on the CDN providing the typescript declaration via X-TypeScript-Types header.

You should probably use @typescript/ata instead.

import { downloadTypesfromPackageName } from '@bigmistqke/repl'

// Download types for a package
const { path, types } = await downloadTypesfromPackageName({
  ts,
  name: 'react',
  cdn: 'https://esm.sh'
})
// Returns: { path: string, types: Record<string, string> }

Monaco Editor Integration

Integrate with Monaco editor for TypeScript support, uses downloadTypesfromPackageName internally:

import { createMonacoTypeDownloader } from '@bigmistqke/repl'

const typeDownloader = createMonacoTypeDownloader({
  ts,
  tsconfig: {
    target: monaco.languages.typescript.ScriptTarget.ESNext,
    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs
  }
})

// Download and register types
await typeDownloader.downloadModule('react')

// Get generated tsconfig
const tsconfig = typeDownloader.tsconfig()

PathUtils

Path manipulation utilities:

import { PathUtils } from '@bigmistqke/repl'

// Get file extension
const ext = PathUtils.getExtension('/src/main.ts') // 'ts'

// Get filename
const name = PathUtils.getName('/src/main.ts') // 'main.ts'

// Get parent directory
const parent = PathUtils.getParentPath('/src/main.ts') // '/src'

// Normalize path (remove leading slashes)
const normalized = PathUtils.normalizePath('//src/main.ts') // 'src/main.ts'

// Resolve relative paths
const resolved = PathUtils.resolvePath('/src/index.js', './utils.js') // '/src/utils.js'

// Check if path is a URL
const isURL = PathUtils.isUrl('https://example.com/file.js') // true

resolvePackageEntries

Resolve package entry points:

import { resolvePackageEntries } from '@bigmistqke/repl'

const entries = resolvePackageEntries(packageJson)

License

MIT

About

πŸ—’οΈ building blocks to transpile and execute code in-browser.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors