Skip to content

Commit

Permalink
Move parsing to workers
Browse files Browse the repository at this point in the history
Signed-off-by: William So <polyipseity@gmail.com>
  • Loading branch information
polyipseity committed Aug 17, 2023
1 parent af0bb30 commit b12e8ec
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-buses-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"obsidian-modules": patch
---

Move parsing to workers.
87 changes: 13 additions & 74 deletions sources/require/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@ import {
dynamicRequire,
dynamicRequireLazy,
dynamicRequireSync,
escapeJavaScriptString,
importable,
} from "@polyipseity/obsidian-plugin-library"
import type { Context, Resolve, Resolved } from "obsidian-modules"
import { type Options, parse, parseExpressionAt } from "acorn"
import { TFile, getLinkpath, normalizePath, requestUrl } from "obsidian"
import type {
Transpile,
TypeScriptTranspile,
WeakCacheIdentity,
} from "./transpile.js"
import { BUNDLE } from "../import.js"
import type { CallExpression } from "estree"
import type { ModulesPlugin } from "../main.js"
import { generate } from "astring"
import { isObject } from "lodash-es"
import { simple } from "acorn-walk"
import { normalizeURL } from "../util.js"
import type { parseAndRewriteRequire as parr } from "../worker.js"

const
tsMorphBootstrap = dynamicRequireLazy<typeof import("@ts-morph/bootstrap")
Expand Down Expand Up @@ -543,7 +539,6 @@ function parseWikilink(
export class ExternalLinkResolve
extends AbstractResolve
implements Resolve {
public static readonly filter = /^https?:/u
protected readonly redirects: Record<string, string> = {}
protected readonly identities: Record<string, "await" | "fail" | {
readonly code: string
Expand Down Expand Up @@ -651,54 +646,13 @@ export class ExternalLinkResolve
} catch (error) {
self.console.debug(error)
}
const reqs: string[] = [],
opts: Options = {
allowAwaitOutsideFunction: false,
allowHashBang: true,
allowImportExportEverywhere: false,
allowReserved: true,
allowReturnOutsideFunction: false,
allowSuperOutsideMethod: false,
ecmaVersion: "latest",
locations: false,
preserveParens: false,
ranges: false,
sourceType: "script",
},
tree = parse(identity2.code, opts)
simple(tree, {
// eslint-disable-next-line @typescript-eslint/naming-convention
CallExpression: node => {
const node2 = node as CallExpression & typeof node,
{ callee } = node2
if (callee.type !== "Identifier" || callee.name !== "require") {
return
}
const [arg0] = node2.arguments
if (arg0?.type !== "Literal" || typeof arg0.value !== "string") {
return
}
const { value } = arg0
if (importable({}, value)) { return }
let prefix = ""
if (!(/^\.{0,2}\//u).test(value)) {
try {
// eslint-disable-next-line no-new
new URL(value)
} catch (error) {
self.console.debug(error)
prefix = "/"
}
}
const value2 = this.normalizeURL(`${prefix}${value}`, href)
if (value2 === null) { return }
node2.callee = parseExpressionAt("self.require", 0, opts)
arg0.raw = escapeJavaScriptString(value2)
reqs.push(arg0.value = value2)
},
})
identity2.code = generate(tree, { comments: true, indent: "" })
await Promise.all(reqs.map(async req => this.aresolve0(req)))
const { code, requires } =
await (await this.context.workerPool).exec<typeof parr>(
"parseAndRewriteRequire",
[{ code: identity2.code, href }],
)
identity2.code = code
await Promise.all(requires.map(async req => this.aresolve0(req)))
} catch (error) {
self.console.debug(error)
}
Expand All @@ -707,25 +661,10 @@ export class ExternalLinkResolve
return [href, identity]
}

protected normalizeURL(id: string, cwd?: string): string | null {
const { filter } = ExternalLinkResolve
let href = null
if (cwd !== void 0) {
try {
({ href } = new URL(id, cwd))
if (!filter.test(href)) { href = null }
} catch (error) {
self.console.debug(error)
}
}
if (href === null) {
try {
({ href } = new URL(id))
if (!filter.test(href)) { href = null }
} catch (error) {
self.console.debug(error)
}
}
protected normalizeURL(
...args: Parameters<typeof normalizeURL>
): ReturnType<typeof normalizeURL> {
const href = normalizeURL(...args)
return href === null ? null : this.redirects[href] ?? href
}

Expand Down
24 changes: 24 additions & 0 deletions sources/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export function normalizeURL(id: string, cwd?: string): string | null {
const { filter } = normalizeURL
let href = null
if (cwd !== void 0) {
try {
({ href } = new URL(id, cwd))
if (!filter.test(href)) { href = null }
} catch (error) {
self.console.debug(error)
}
}
if (href === null) {
try {
({ href } = new URL(id))
if (!filter.test(href)) { href = null }
} catch (error) {
self.console.debug(error)
}
}
return href
}
export namespace normalizeURL {
export const filter = /^https?:/u
}
96 changes: 95 additions & 1 deletion sources/worker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
import { type Options, parse, parseExpressionAt } from "acorn"
import { createProject, type ts } from "@ts-morph/bootstrap"
import type { CallExpression } from "estree"
import { generate } from "astring"
import { isNil } from "lodash-es"
import { normalizeURL } from "./util.js"
import { simple } from "acorn-walk"
import { worker } from "workerpool"

worker({ tsc }, {})
worker({ parseAndRewriteRequire, tsc }, {})

function escapeJavaScriptString(value: string): string {
return `\`${value.replace(/(?<char>`|\\|\$)/ug, "\\$<char>")}\``
}

function dynamicRequireSync<T>(module: string): T {
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const ret: unknown = require(module)
if (isNil(ret)) { throw new Error(module) }
return ret as T
}

function importable(
...args: Parameters<typeof dynamicRequireSync>
): boolean {
try {
dynamicRequireSync(...args)
return true
} catch (error) {
self.console.debug(error)
return false
}
}

export async function tsc(input: tsc.Input): Promise<tsc.Output> {
const { content, compilerOptions } = input,
Expand Down Expand Up @@ -30,3 +59,68 @@ export namespace tsc {
}
export type Output = string
}

export function parseAndRewriteRequire(
input: parseAndRewriteRequire.Input,
): parseAndRewriteRequire.Output {
const requires: string[] = [],
opts: Options = {
allowAwaitOutsideFunction: false,
allowHashBang: true,
allowImportExportEverywhere: false,
allowReserved: true,
allowReturnOutsideFunction: false,
allowSuperOutsideMethod: false,
ecmaVersion: "latest",
locations: false,
preserveParens: false,
ranges: false,
sourceType: "script",
},
tree = parse(input.code, opts)
simple(tree, {
// eslint-disable-next-line @typescript-eslint/naming-convention
CallExpression: node => {
const node2 = node as CallExpression & typeof node,
{ callee } = node2
if (callee.type !== "Identifier" || callee.name !== "require") {
return
}
const [arg0] = node2.arguments
if (arg0?.type !== "Literal" || typeof arg0.value !== "string") {
return
}
const { value } = arg0
if (importable(value)) { return }
let prefix = ""
if (!(/^\.{0,2}\//u).test(value)) {
try {
// eslint-disable-next-line no-new
new URL(value)
} catch (error) {
self.console.debug(error)
prefix = "/"
}
}
const value2 = normalizeURL(`${prefix}${value}`, input.href)
if (value2 === null) { return }
node2.callee = parseExpressionAt("self.require", 0, opts)
arg0.raw = escapeJavaScriptString(value2)
requires.push(arg0.value = value2)
},
})
return {
code: generate(tree, { comments: true, indent: "" }),
requires,
}
}
export namespace parseAndRewriteRequire {
export interface Input {
readonly code: string
readonly href: string
}
export interface Output {
readonly code: string
readonly requires: readonly string[]
}
}

0 comments on commit b12e8ec

Please sign in to comment.