Skip to content

Commit

Permalink
fix: switch from tsc watch to chokidar (medusajs#11110)
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage authored and jimrarras committed Jan 28, 2025
1 parent f162c55 commit f84997a
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 77 deletions.
6 changes: 6 additions & 0 deletions .changeset/dry-insects-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/framework": patch
---

fix: switch from tsc watch to chokidar
1 change: 1 addition & 0 deletions packages/core/framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@medusajs/workflows-sdk": "~2.3.1",
"@opentelemetry/api": "^1.9.0",
"@types/express": "^4.17.17",
"chokidar": "^3.4.2",
"compression": "1.7.4",
"connect-redis": "5.2.0",
"cookie-parser": "^1.4.6",
Expand Down
146 changes: 71 additions & 75 deletions packages/core/framework/src/build-tools/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import path from "path"
import { getConfigFile } from "@medusajs/utils"
import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"
import { rm, access, constants, copyFile, writeFile, mkdir } from "fs/promises"
import chokidar from "chokidar"
import type tsStatic from "typescript"
import { FileSystem, getConfigFile } from "@medusajs/utils"
import { rm, access, constants, copyFile } from "fs/promises"
import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"

/**
* The compiler exposes the opinionated APIs for compiling Medusa
Expand All @@ -27,7 +28,6 @@ export class Compiler {
#adminSourceFolder: string
#pluginsDistFolder: string
#backendIgnoreFiles: string[]
#pluginOptionsPath: string
#adminOnlyDistFolder: string
#tsCompiler?: typeof tsStatic

Expand All @@ -38,10 +38,6 @@ export class Compiler {
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
this.#pluginsDistFolder = path.join(this.#projectRoot, ".medusa/server")
this.#pluginOptionsPath = path.join(
this.#projectRoot,
".medusa/server/medusa-plugin-options.json"
)
this.#backendIgnoreFiles = [
"integration-tests",
"test",
Expand Down Expand Up @@ -141,13 +137,14 @@ export class Compiler {
}

/**
* Ensures a directory exists
* Returns a boolean indicating if a file extension belongs
* to a JavaScript or TypeScript file
*/
async #ensureDir(path: string, clean?: boolean) {
if (clean) {
await this.#clean(path)
#isScriptFile(filePath: string) {
if (filePath.endsWith(".ts") && !filePath.endsWith(".d.ts")) {
return true
}
await mkdir(path, { recursive: true })
return filePath.endsWith(".js")
}

/**
Expand All @@ -167,24 +164,6 @@ export class Compiler {
return { configFilePath, configModule }
}

/**
* Creates medusa-plugin-options.json file that contains some
* metadata related to the plugin, which could be helpful
* for MedusaJS loaders during development
*/
async #createPluginOptionsFile() {
await writeFile(
this.#pluginOptionsPath,
JSON.stringify(
{
srcDir: path.join(this.#projectRoot, "src"),
},
null,
2
)
)
}

/**
* Prints typescript diagnostic messages
*/
Expand Down Expand Up @@ -437,7 +416,7 @@ export class Compiler {
*/
const { emitResult, diagnostics } = await this.#emitBuildOutput(
tsConfig,
["integration-tests", "test", "unit-tests", "src/admin"],
this.#backendIgnoreFiles,
dist
)

Expand Down Expand Up @@ -472,54 +451,71 @@ export class Compiler {
* The "onFileChange" argument can be used to get notified when
* a file has changed.
*/
async developPluginBackend(onFileChange?: () => void) {
await this.#ensureDir(this.#pluginsDistFolder, true)
await this.#createPluginOptionsFile()
const ts = await this.#loadTSCompiler()
async developPluginBackend(
transformer: (filePath: string) => Promise<string>,
onFileChange?: (
filePath: string,
action: "add" | "change" | "unlink"
) => void
) {
const fs = new FileSystem(this.#pluginsDistFolder)
await fs.createJson("medusa-plugin-options.json", {
srcDir: path.join(this.#projectRoot, "src"),
})

/**
* Format host is needed to print diagnostic messages
*/
const formatHost: tsStatic.FormatDiagnosticsHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
}
const watcher = chokidar.watch(["."], {
ignoreInitial: true,
cwd: this.#projectRoot,
ignored: [
/(^|[\\/\\])\../,
"node_modules",
"dist",
"static",
"private",
".medusa/**/*",
...this.#backendIgnoreFiles,
],
})

/**
* Creating a watcher compiler host to watch files and recompile
* them as they are changed
*/
const host = ts.createWatchCompilerHost(
this.#tsConfigPath,
{
outDir: this.#pluginsDistFolder,
noCheck: true,
},
ts.sys,
ts.createEmitAndSemanticDiagnosticsBuilderProgram,
(diagnostic) => this.#printDiagnostics(ts, [diagnostic]),
(diagnostic) => {
if (typeof diagnostic.messageText === "string") {
this.#logger.info(diagnostic.messageText)
} else {
this.#logger.info(
ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost)
)
}
},
{
excludeDirectories: this.#backendIgnoreFiles,
watcher.on("add", async (file) => {
if (!this.#isScriptFile(file)) {
return
}
)
const relativePath = path.relative(this.#projectRoot, file)
const outputPath = relativePath.replace(/\.ts$/, ".js")

const origPostProgramCreate = host.afterProgramCreate
host.afterProgramCreate = (program) => {
origPostProgramCreate!(program)
onFileChange?.()
}
this.#logger.info(`${relativePath} updated: Republishing changes`)
await fs.create(outputPath, await transformer(file))

ts.createWatchProgram(host)
onFileChange?.(file, "add")
})
watcher.on("change", async (file) => {
if (!this.#isScriptFile(file)) {
return
}
const relativePath = path.relative(this.#projectRoot, file)
const outputPath = relativePath.replace(/\.ts$/, ".js")

this.#logger.info(`${relativePath} updated: Republishing changes`)
await fs.create(outputPath, await transformer(file))

onFileChange?.(file, "change")
})
watcher.on("unlink", async (file) => {
if (!this.#isScriptFile(file)) {
return
}
const relativePath = path.relative(this.#projectRoot, file)
const outputPath = relativePath.replace(/\.ts$/, ".js")

this.#logger.info(`${relativePath} removed: Republishing changes`)
await fs.remove(outputPath)
onFileChange?.(file, "unlink")
})

watcher.on("ready", () => {
this.#logger.info("watching for file changes")
})
}

async buildPluginAdminExtensions(bundler: {
Expand Down
56 changes: 54 additions & 2 deletions packages/medusa/src/commands/plugin/develop.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "path"
import * as swcCore from "@swc/core"
import { execFile } from "child_process"
import { logger } from "@medusajs/framework/logger"
import { Compiler } from "@medusajs/framework/build-tools"
Expand All @@ -10,9 +11,18 @@ export default async function developPlugin({
}) {
let isBusy = false
const compiler = new Compiler(directory, logger)
const parsedConfig = await compiler.loadTSConfigFile()
if (!parsedConfig) {
return
}

const yalcBin = path.join(path.dirname(require.resolve("yalc")), "yalc.js")

await compiler.developPluginBackend(async () => {
/**
* Publishes the build output to the registry and updates
* installations
*/
function publishChanges() {
/**
* Here we avoid multiple publish calls when the filesystem is
* changed too quickly. This might result in stale content in
Expand Down Expand Up @@ -46,5 +56,47 @@ export default async function developPlugin({
console.error(stderr)
}
)
})
}

/**
* Transforms a given file using @swc/core
*/
async function transformFile(filePath: string) {
const output = await swcCore.transformFile(filePath, {
sourceMaps: "inline",
module: {
type: "commonjs",
strictMode: true,
noInterop: false,
},
jsc: {
externalHelpers: false,
target: "es2021",
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
dynamicImport: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
react: {
throwIfNamespace: false,
useBuiltins: false,
pragma: "React.createElement",
pragmaFrag: "React.Fragment",
importSource: "react",
runtime: "automatic",
},
},
keepClassNames: true,
baseUrl: directory,
},
})
return output.code
}

await compiler.buildPluginBackend(parsedConfig)
await compiler.developPluginBackend(transformFile, publishChanges)
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5766,6 +5766,7 @@ __metadata:
"@types/express": ^4.17.17
"@types/jsonwebtoken": ^8.5.9
awilix: ^8.0.1
chokidar: ^3.4.2
compression: 1.7.4
connect-redis: 5.2.0
cookie-parser: ^1.4.6
Expand Down

0 comments on commit f84997a

Please sign in to comment.