fix: switch from tsc watch to chokidar (#11110)
This commit is contained in:
6
.changeset/dry-insects-burn.md
Normal file
6
.changeset/dry-insects-burn.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/framework": patch
|
||||
---
|
||||
|
||||
fix: switch from tsc watch to chokidar
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +28,6 @@ export class Compiler {
|
||||
#adminSourceFolder: string
|
||||
#pluginsDistFolder: string
|
||||
#backendIgnoreFiles: string[]
|
||||
#pluginOptionsPath: string
|
||||
#adminOnlyDistFolder: string
|
||||
#tsCompiler?: typeof tsStatic
|
||||
|
||||
@@ -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",
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
*/
|
||||
@@ -437,7 +416,7 @@ export class Compiler {
|
||||
*/
|
||||
const { emitResult, diagnostics } = await this.#emitBuildOutput(
|
||||
tsConfig,
|
||||
["integration-tests", "test", "unit-tests", "src/admin"],
|
||||
this.#backendIgnoreFiles,
|
||||
dist
|
||||
)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user