@darkobits/ts
Version:
Vite-based toolchain for Node projects.
152 lines (151 loc) • 6.3 kB
JavaScript
import path from "node:path";
import { interopImportDefault } from "@darkobits/interop-import-default";
import typescriptPlugin from "@rollup/plugin-typescript";
import chalk from "chalk";
import glob from "fast-glob";
import preserveShebangPlugin from "rollup-plugin-preserve-shebang";
import { viteStaticCopy } from "vite-plugin-static-copy";
import tsconfigPathsPluginExport from "vite-tsconfig-paths";
import { TEST_FILE_PATTERNS, BARE_EXTENSIONS } from "../etc/constants.js";
import cleanupPlugin from "../lib/cleanup-plugin.js";
import executablePlugin from "../lib/executable-plugin.js";
import log from "../lib/log.js";
import { nodeExternalPlugin } from "../lib/node-external-plugin.js";
import tscAliasPlugin from "../lib/tsc-alias-plugin.js";
import { createViteConfigurationPreset } from "../lib/utils.js";
const tsconfigPathsPlugin = interopImportDefault(tsconfigPathsPluginExport);
const isWatchMode = process.argv.includes("--watch") || process.argv.includes("-w");
const node = createViteConfigurationPreset(async (context) => {
const prefix = chalk.dim.cyan("preset:node");
log.isReady();
const { command, mode, root, srcDir, outDir, patterns: { SOURCE_FILES, TEST_FILES } } = context;
if (command === "serve" && mode !== "test") {
log.error(prefix, `The ${chalk.white("serve")} command is not available when using the ${chalk.cyan("node")} preset.`);
process.exit(1);
}
const [
// Array of all source files. To prevent bundling, we tell Rollup that each
// file in the project should be treated as its own entrypoint.
entry,
// Array of all files in the source directory other than source code.
filesToCopy
// Whether the user has an ESLint configuration file. If not, we skip adding
// the ESLint plugin to the build.
// eslintConfigResult
] = await Promise.all([
glob(SOURCE_FILES, { cwd: root, ignore: [TEST_FILES] }),
glob([`${srcDir}/**/*`, `!${SOURCE_FILES}`, `!${TEST_FILES}`], { cwd: root })
]);
if (entry.length === 0) {
log.error(prefix, `No entry files found in ${chalk.green(srcDir)}.`);
process.exit(1);
}
const { config, packageJson } = context;
const packageType = packageJson.type === "module" ? "EXPLICIT_ESM" : packageJson.type === "commonjs" ? "EXPLICIT_CJS" : !packageJson.type ? "IMPLICIT_CJS" : "INVALID";
config.build = {
// Use the inferred output directory defined in tsconfig.json.
outDir,
emptyOutDir: true,
// We don't need to minify this kind of project.
minify: false,
sourcemap: true,
lib: {
entry,
fileName: "[name]",
// Infer output format based on the "type" setting in package.json.
formats: packageType === "EXPLICIT_ESM" ? ["es"] : ["cjs"]
},
rollupOptions: {
logLevel: "silent",
output: {
preserveModules: true,
preserveModulesRoot: srcDir
}
}
};
const packageTypeLabel = {
IMPLICIT_CJS: "cjs",
EXPLICIT_CJS: "cjs",
EXPLICIT_ESM: "esm",
INVALID: ""
};
if (command === "build") {
process.stdout.write("\n");
await log.info(prefix, [
chalk.green(path.parse(srcDir).name),
chalk.whiteBright("→"),
chalk.green(path.parse(outDir).name),
`${chalk.gray("as")} ${chalk.green(packageTypeLabel[packageType])}`,
isWatchMode && `${chalk.gray("watch:")}${chalk.green("true")}`
].filter(Boolean).join(" "));
process.stdout.write("\n");
}
config.test = {
name: packageJson.name,
root,
include: [TEST_FILES],
deps: {
// Automatically fixes default imports of modules where the intended value
// is on an object on the `default` property.
interopDefault: true
},
coverage: {
all: true,
// Files to be considered as source when computing coverage. Vitest
// requires paths relative to the configured root, and entry files are
// already resolved to absolute paths. This un-resolves them to relative
// paths.
include: entry.map((entry2) => path.relative(root, entry2))
}
};
config.plugins.push(typescriptPlugin({
// Ensures we don't emit declarations for test files.
exclude: [`**/*.{${TEST_FILE_PATTERNS.join(",")}}.{${BARE_EXTENSIONS.join(",")}}`],
// If TypeScript sees .ts files in the project root (configuration files,
// for example) it will assume that they need to be compiled and use the
// project root as a reference for the directory structure it needs to
// create in the output folder. To prevent this, specify the configured
// source folder as the root of the compilation.
filterRoot: srcDir,
compilerOptions: {
baseUrl: srcDir,
// Ensure we only emit declaration files; all other source should be
// processed by Vite/Rollup.
emitDeclarationOnly: true,
// Only fail the compilation on type-errors when building for production.
// This prevents things like Vitest from failing when in watch mode due to
// trivial errors like a variable not being used, etc.
noEmitOnError: !isWatchMode && mode === "production",
// This MUST be set to the same value as config.build.sourcemap or the
// plugin will throw an error.
sourceMap: config.build.sourcemap,
// The plugin will issue a warning if this is set a value other than
// 'ESNext'. Because we are only using it to emit declaration files, this
// setting has no effect on Rollup's output.
module: "ESNext"
}
}));
config.plugins.push(tsconfigPathsPlugin({ root }));
const { tsConfigPath } = context;
config.plugins.push(tscAliasPlugin({ configFile: tsConfigPath }));
config.plugins.push(nodeExternalPlugin({ root }));
config.plugins.push(preserveShebangPlugin());
config.plugins.push(executablePlugin());
if (filesToCopy.length > 0) {
config.plugins.push(viteStaticCopy({
targets: filesToCopy.map((filePath) => {
const src = path.relative(root, filePath);
const dest = path.dirname(src).split(path.sep).slice(1).join(path.sep);
return { src, dest };
})
}));
}
config.plugins.push(cleanupPlugin({
// Removes empty .js files created when .ts files only export types.
removeEmptyChunks: true
}));
});
export {
node
};
//# sourceMappingURL=vite.js.map