@embeddable.com/sdk-core
Version:
Core Embeddable SDK module responsible for web-components bundling and publishing.
240 lines (206 loc) • 6.57 kB
JavaScript
/**
* Most of the code in this file is copied from the following file:
* https://github.com/swc-project/swc-node/blob/master/packages/register/esm.mts
*
* The main difference is that resolve function is modified to handle non-TS files.
*
* */
import { fileURLToPath, pathToFileURL } from "url";
import { dirname, join } from "path";
import ts from "typescript";
import { readDefaultTsConfig } from "@swc-node/register/read-default-tsconfig";
import { compile } from "./custom-esm-register.cjs";
import fs from "fs";
import path from "path";
// Find the tsconfig.json in the customer's project directory
const tsconfigPath = ts.findConfigFile(
process.cwd(),
fs.existsSync,
"tsconfig.json",
);
let tsconfig;
if (tsconfigPath) {
// Read and parse the tsconfig.json
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
dirname(tsconfigPath),
);
tsconfig = parsedConfig.options;
} else {
tsconfig = await readDefaultTsConfig();
}
// Set necessary compiler options
tsconfig.module = ts.ModuleKind.ESNext;
tsconfig.moduleResolution = ts.ModuleResolutionKind.Node16;
const moduleResolutionCache = ts.createModuleResolutionCache(
ts.sys.getCurrentDirectory(),
(x) => x,
tsconfig,
);
const host = {
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
};
const EXTENSIONS = [ts.Extension.Ts, ts.Extension.Tsx, ts.Extension.Mts];
const NON_JS_TS_EXTENSIONS =
/\.(css|scss|less|sass|styl|json|svg|woff|woff2|png|jpg|jpeg|gif|webp|md|yml|yaml)$/;
const currentDir = dirname(fileURLToPath(import.meta.url));
const isWindows = process.platform === "win32";
function cleanWindowsFileURL(url) {
// Remove any duplicate file:/ prefixes and normalize slashes
return url
.replace(/file:\/+/g, "file:///")
.replace(/\\/g, "/")
.replace(/file:\/+([A-Za-z]:)/, "file:///$1");
}
export const resolve = async (specifier, context, nextResolve) => {
if (NON_JS_TS_EXTENSIONS.test(specifier)) {
const mockModulePath = pathToFileURL(
join(currentDir, "mock-module.js"),
).href;
return {
format: "module",
url: mockModulePath,
shortCircuit: true,
};
}
const isTS = EXTENSIONS.some((ext) => specifier.endsWith(ext));
// Entry point
if (!context.parentURL) {
if (isWindows) {
// Handle the case where the specifier contains both the cwd and the actual path
const parts = specifier.split("file:/");
const lastPart = parts[parts.length - 1];
// Convert the path to a proper file URL
const cleanPath = lastPart.replace(/^\/+/, "");
const entryPointPath = pathToFileURL(cleanPath).href;
return {
format: isTS ? "ts" : undefined,
url: cleanWindowsFileURL(entryPointPath),
shortCircuit: true,
};
}
return {
format: isTS ? "ts" : undefined,
url: specifier,
shortCircuit: true,
};
}
// Handle SDK relative imports
if (
isWindows &&
specifier.startsWith(".") &&
context.parentURL?.includes("embeddable-sdk")
) {
const parentPath = fileURLToPath(context.parentURL);
const parentDir = dirname(parentPath);
let resolvedPath = join(parentDir, specifier);
// If no extension is provided, try common extensions
if (!path.extname(resolvedPath)) {
const extensions = [".js", ".mjs", ".cjs", ".ts", ".mts"];
for (const ext of extensions) {
const pathWithExt = resolvedPath + ext;
if (fs.existsSync(pathWithExt) && fs.statSync(pathWithExt).isFile()) {
resolvedPath = pathWithExt;
break;
}
}
}
// Check if the resolved path exists and is a file
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) {
return {
url: pathToFileURL(resolvedPath).href,
shortCircuit: true,
};
}
}
// Determine if this is a package import
const isPackageImport =
!specifier.startsWith(".") &&
!specifier.startsWith("/") &&
!/^[A-Z]:/.test(specifier); // Windows path
// External library import
if (
(context.parentURL?.includes("/node_modules/") || isPackageImport) &&
!isTS
) {
try {
return await nextResolve(specifier);
} catch (err) {
if (
!path.extname(specifier) &&
(err.code === "ERR_MODULE_NOT_FOUND" ||
err.code === "ERR_UNSUPPORTED_DIR_IMPORT")
) {
try {
return await nextResolve(specifier + ".js");
} catch {
// fall through
}
try {
return await nextResolve(specifier + "/index.js");
} catch {
// fall through
}
}
throw err;
}
}
const isFileUrl = specifier.startsWith("file://");
const isParentFileUrl = context.parentURL?.startsWith("file://");
// Resolve the module using TypeScript's module resolution
const { resolvedModule } = ts.resolveModuleName(
isFileUrl ? fileURLToPath(specifier) : specifier,
isParentFileUrl ? fileURLToPath(context.parentURL) : context.parentURL,
tsconfig,
host,
moduleResolutionCache,
);
// Local project TS file
if (
resolvedModule &&
!resolvedModule.resolvedFileName.includes("/node_modules/") &&
EXTENSIONS.includes(resolvedModule.extension)
) {
const resolved = {
format: "ts",
url:
pathToFileURL(resolvedModule.resolvedFileName).href +
`?update=${Date.now()}`,
shortCircuit: true,
};
return resolved;
}
// Fallback for non-TS files or unresolved modules
const specifierPathOrUrl =
!resolvedModule?.resolvedFileName?.includes("/node_modules/") && isWindows
? pathToFileURL(specifier).href
: specifier;
return nextResolve(specifierPathOrUrl);
};
export const load = async (url, context, nextLoad) => {
if (context.format === "ts") {
const { source } = await nextLoad(url, context);
const code =
typeof source === "string" ? source : Buffer.from(source).toString();
// Compile the code using @swc-node with the customer's tsconfig
const compiled = await compile(code, fileURLToPath(url), tsconfig, true);
const resolved = {
format: "module",
source: compiled,
shortCircuit: true,
};
return resolved;
} else {
if (
isWindows &&
!url.startsWith("file://") &&
url.includes("node_modules")
) {
return nextLoad(pathToFileURL(url).href, context);
}
return nextLoad(url, context);
}
};