Skip to content

Commit

Permalink
feat!: rewrite with typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Oct 20, 2021
1 parent 1c61d78 commit b085827
Show file tree
Hide file tree
Showing 17 changed files with 597 additions and 172 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Expand Up @@ -21,3 +21,4 @@ jobs:
- run: yarn install
- run: yarn lint
- run: yarn test

9 changes: 9 additions & 0 deletions build.config.ts
@@ -0,0 +1,9 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
declaration: true,
emitCJS: true,
entries: [
'src/index'
]
})
96 changes: 0 additions & 96 deletions lib/index.d.ts

This file was deleted.

5 changes: 0 additions & 5 deletions lib/index.mjs

This file was deleted.

20 changes: 13 additions & 7 deletions package.json
Expand Up @@ -6,16 +6,21 @@
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": "./lib/index.mjs",
"main": "./lib/index.mjs",
"types": "./lib/index.d.ts",
"exports": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"lib"
"dist"
],
"scripts": {
"build": "unbuild",
"lint": "eslint --ext .mjs lib",
"release": "yarn test && standard-version && npm publish && git push --follow-tags",
"test": "mocha ./test/**/*.test.mjs"
"release": "yarn test && yarn build && standard-version && npm publish && git push --follow-tags",
"test": "yarn build && mocha ./test/**/*.test.mjs"
},
"dependencies": {
"import-meta-resolve": "^1.1.1"
Expand All @@ -27,6 +32,7 @@
"eslint": "latest",
"jiti": "^1.12.5",
"mocha": "^9.1.2",
"standard-version": "latest"
"standard-version": "latest",
"unbuild": "^0.5.7"
}
}
12 changes: 6 additions & 6 deletions lib/_utils.mjs → src/_utils.ts
@@ -1,31 +1,31 @@
import { builtinModules } from 'node:module'
import { builtinModules } from 'module'

export const BUILTIN_MODULES = new Set(builtinModules)

export function normalizeSlash (str) {
export function normalizeSlash(str) {
return str.replace(/\\/g, '/')
}

export function pcall (fn, ...args) {
export function pcall(fn, ...args) {
try {
return Promise.resolve(fn(...args)).catch(err => perr(err))
} catch (err) {
return perr(err)
}
}

export function perr (_err) {
export function perr(_err) {
const err = new Error(_err)
err.code = _err.code
Error.captureStackTrace(err, pcall)
return Promise.reject(err)
}

export function isObject (val) {
export function isObject(val) {
return val !== null && typeof val === 'object'
}

export function matchAll (regex, string, addition) {
export function matchAll(regex, string, addition) {
const matches = []
for (const match of string.matchAll(regex)) {
matches.push({
Expand Down
60 changes: 54 additions & 6 deletions lib/analyze.mjs → src/analyze.ts
@@ -1,4 +1,52 @@
import { matchAll } from './_utils.mjs'
import { matchAll } from './_utils'

export interface ESMImport {
type: 'static' | 'dynamic'
code: string
start: number
end: number
}

export interface StaticImport extends ESMImport {
type: 'static'
imports: string
specifier: string
}

export interface ParsedStaticImport extends StaticImport {
defaultImport?: string
namespacedImport?: string
namedImports?: { [name: string]: string }
}

export interface DynamicImport extends ESMImport {
type: 'dynamic'
expression: string
}

export interface ESMExport {
type: 'declaration' | 'named' | 'default',
code: string
start: number
end: number
}

export interface DeclarationExport extends ESMExport {
type: 'declaration'
declaration: string
name: string
}

export interface NamedExport extends ESMExport {
type: 'named'
exports: string
names: string[]
}

export interface DefaultExport extends ESMExport {
type: 'default'
}


export const ESM_STATIC_IMPORT_RE = /^(?<=\s*)import\s*(["'\s]*(?<imports>[\w*${}\n\r\t, /]+)from\s*)?["']\s*(?<specifier>.*[@\w_-]+)\s*["'][^\n]*$/gm
export const DYNAMIC_IMPORT_RE = /import\s*\((?<expression>(?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gm
Expand All @@ -7,15 +55,15 @@ export const EXPORT_DECAL_RE = /\bexport\s+(?<declaration>(function|let|const|va
const EXPORT_NAMED_RE = /\bexport\s+{(?<exports>[^}]+)}/g
const EXPORT_DEFAULT_RE = /\bexport\s+default\s+/g

export function findStaticImports (code) {
export function findStaticImports(code: string): StaticImport[] {
return matchAll(ESM_STATIC_IMPORT_RE, code, { type: 'static' })
}

export function findDynamicImports (code) {
export function findDynamicImports(code: string): DynamicImport[] {
return matchAll(DYNAMIC_IMPORT_RE, code, { type: 'dynamic' })
}

export function parseStaticImport (matched) {
export function parseStaticImport(matched: StaticImport): ParsedStaticImport {
const cleanedImports = (matched.imports || '')
.replace(/(\/\/[^\n]*\n|\/\*.*\*\/)/g, '')
.replace(/\s+/g, ' ')
Expand All @@ -36,10 +84,10 @@ export function parseStaticImport (matched) {
defaultImport,
namespacedImport,
namedImports
}
} as ParsedStaticImport
}

export function findExports (code) {
export function findExports(code: string): ESMExport[] {
const declaredExports = matchAll(EXPORT_DECAL_RE, code, { type: 'declaration' })

const namedExports = matchAll(EXPORT_NAMED_RE, code, { type: 'named' })
Expand Down
30 changes: 18 additions & 12 deletions lib/cjs.mjs → src/cjs.ts
@@ -1,27 +1,33 @@

import { createRequire } from 'node:module'
import { dirname } from 'node:path'
import { fileURLToPath } from './utils.mjs'
import { isObject } from './_utils.mjs'
import { createRequire } from 'module'
import { dirname } from 'path'
import { fileURLToPath } from './utils'
import { isObject } from './_utils'

export function createCommonJS (url) {
export interface CommonjsContext {
__filename: string
__dirname: string
require: NodeRequire
}

export function createCommonJS(url: string): CommonjsContext {
const __filename = fileURLToPath(url)
const __dirname = dirname(__filename)

// Lazy require
let _nativeRequire
const getNativeRequire = () => _nativeRequire || (_nativeRequire = createRequire(url))
function require (id) { return getNativeRequire()(id) }
function require(id) { return getNativeRequire()(id) }
require.resolve = (id, options) => getNativeRequire().resolve(id, options)

return {
__filename,
__dirname,
require
}
} as CommonjsContext
}

export function interopDefault (sourceModule) {
export function interopDefault(sourceModule: any): any {
if (!isObject(sourceModule) || !('default' in sourceModule)) {
return sourceModule
}
Expand All @@ -33,20 +39,20 @@ export function interopDefault (sourceModule) {
Object.defineProperty(newModule, key, {
enumerable: false,
configurable: false,
get () { return newModule }
get() { return newModule }
})
}
} catch (_err) {}
} catch (_err) { }
} else {
try {
if (!(key in newModule)) {
Object.defineProperty(newModule, key, {
enumerable: true,
configurable: true,
get () { return sourceModule[key] }
get() { return sourceModule[key] }
})
}
} catch (_err) {}
} catch (_err) { }
}
}
return newModule
Expand Down
23 changes: 13 additions & 10 deletions lib/eval.mjs → src/eval.ts
@@ -1,31 +1,34 @@
import { resolve } from './resolve.mjs'
import { loadURL, toDataURL } from './utils.mjs'
import { resolve } from './resolve'
import { loadURL, toDataURL } from './utils'
import type { ResolveOptions } from './resolve'

export interface EvaluateOptions extends ResolveOptions {
url?: string
}

const EVAL_ESM_IMPORT_RE = /(?<=import .* from ['"])([^'"]+)(?=['"])|(?<=export .* from ['"])([^'"]+)(?=['"])|(?<=import\s*['"])([^'"]+)(?=['"])|(?<=import\s*\(['"])([^'"]+)(?=['"]\))/g

export async function loadModule (id, opts = {}) {
export async function loadModule(id: string, opts: EvaluateOptions = {}): Promise<any> {
const url = await resolve(id, opts)
const code = await loadURL(url)
return evalModule(code, { ...opts, url })
}

export async function evalModule (code, opts = {}) {
export async function evalModule(code: string, opts: EvaluateOptions = {}): Promise<any> {
const transformed = await transformModule(code, opts)
const dataURL = toDataURL(transformed, opts)
const dataURL = toDataURL(transformed)
return import(dataURL).catch((err) => {
err.stack = err.stack.replace(new RegExp(dataURL, 'g'), opts.url || '_mlly_eval_.mjs')
err.stack = err.stack.replace(new RegExp(dataURL, 'g'), opts.url || '_mlly_eval_')
throw err
})
}

export async function transformModule (code, opts) {
export async function transformModule(code: string, opts: EvaluateOptions): Promise<string> {
// Convert JSON to module
if (opts.url && opts.url.endsWith('.json')) {
return 'export default ' + code
}

// Resolve relative imports
code = await resolveImports(code, opts)

// Rewrite import.meta.url
if (opts.url) {
Expand All @@ -35,7 +38,7 @@ export async function transformModule (code, opts) {
return code
}

export async function resolveImports (code, opts) {
export async function resolveImports(code: string, opts: EvaluateOptions): Promise<string> {
const imports = Array.from(code.matchAll(EVAL_ESM_IMPORT_RE)).map(m => m[0])
if (!imports.length) {
return code
Expand Down

0 comments on commit b085827

Please sign in to comment.