Skip to content

Commit

Permalink
refactor: Add esbuild strategy.
Browse files Browse the repository at this point in the history
  • Loading branch information
darkobits committed Jun 25, 2023
1 parent 5f8dd21 commit ebfa728
Show file tree
Hide file tree
Showing 7 changed files with 1,464 additions and 602 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": [
"endemolshinegroup"
"endemolshinegroup",
"outfile"
],
"eslint.experimental.useFlatConfig": true,
"eslint.options.overrideConfigFile": "eslint.config.mjs"
Expand Down
5 changes: 5 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ts } from '@darkobits/eslint-plugin';

export default [
{
files: [
'eslint.config.mjs'
]
},
...ts,
{
rules: {
Expand Down
1,942 changes: 1,372 additions & 570 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,24 @@
"@darkobits/fd-name": "^0.1.7",
"@darkobits/log": "^2.0.0-beta.15",
"@darkobits/valida": "^0.1.6",
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
"@types/yargs": "^17.0.24",
"aggregate-error": "~3.1.0",
"babel-plugin-module-resolver-tsconfig": "^1.0.25",
"camelcase-keys": "~7.0.2",
"cosmiconfig": "^8.2.0",
"deepmerge": "^4.3.1",
"esbuild": "^0.18.8",
"find-up": "^5.0.0",
"fs-extra": "^11.1.1",
"node-version": "^3.0.0",
"pkg-dir": "~5.0.0",
"read-pkg-up": "~7.0.1",
"resolve-pkg": "^2.0.0",
"ts-import": "^5.0.0-beta.0",
"ts-node": "^10.9.1",
"tsconfck": "^2.1.1",
"tsconfig-paths": "^4.2.0",
"vite": "^4.3.9",
"yargs": "^17.7.2"
},
"devDependencies": {
Expand Down
109 changes: 81 additions & 28 deletions src/lib/configuration-loader.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,88 @@
import path from 'path';

import { dirname } from '@darkobits/fd-name';
import { TsconfigPathsPlugin } from '@esbuild-plugins/tsconfig-paths';
import AggregateError from 'aggregate-error';
import merge from 'deepmerge';
import * as esbuild from 'esbuild';
import findUp from 'find-up';
import fs from 'fs-extra';
import currentNodeVersion from 'node-version';
import packageDirectory from 'pkg-dir';
import readPkgUp from 'read-pkg-up';
import resolvePkg from 'resolve-pkg';
import * as tsImport from 'ts-import';
import {
register,
type RegisterOptions
} from 'ts-node';
import { loadConfigFromFile } from 'vite';
import { find } from 'tsconfck';

import log from 'lib/log';

import type { Loader } from 'cosmiconfig';


async function withViteLoader(configRoot: string, configFile: string) {
const result = await loadConfigFromFile(
{
command: 'build',
mode: 'development'
},
configFile, // configFile?: string,
configRoot // configRoot: string = process.cwd(),
// logLevel?: LogLevel
);
/**
* @private
*
* Uses esbuild to transpile the user's configuration file.
*/
async function withEsbuild(filePath: string) {
const pkg = await readPkgUp({ cwd: path.dirname(filePath) });

const fileName = path.basename(filePath);

const outFileBasename = fileName
.replace(/\.(ts|tsx)$/, '.js')
.replace(/\.cts$/, '.cjs')
.replace(/\.mts$/, '.mjs')
.replace(/\.jsx$/, '.js');

const isExplicitCommonJs = fileName.endsWith('.cjs') || fileName.endsWith('.cts');

const format = isExplicitCommonJs
? 'cjs'
: pkg?.packageJson.type === 'module'
? 'esm'
: 'cjs';

log.verbose(log.prefix('esbuild'), `Using format: ${log.chalk.bold(format)}`);

const tempFileName = `.temp-saffron.${outFileBasename}`;
const tempFilePath = path.join(path.dirname(filePath), tempFileName);

try {
const buildOptions: esbuild.BuildOptions = {
entryPoints: [filePath],
target: `node${currentNodeVersion.major}`,
outfile: tempFileName,
format,
plugins: []
};

const tsConfigFilePath = await find(filePath);

if (tsConfigFilePath) {
log.verbose(log.prefix('esbuild'), `Using TypeScript configuration: ${log.chalk.green(tsConfigFilePath)}`);

buildOptions.tsconfig = tsConfigFilePath;

buildOptions.plugins?.push(TsconfigPathsPlugin({
tsconfig: tsConfigFilePath
}));
}

await esbuild.build(buildOptions);
const result = await import(tempFilePath);
// await fs.remove(tempFilePath);

// @ts-expect-error
return result?.config?.default ?? result?.config;
return result?.default ?? result;
} finally {
if (await fs.exists(tempFilePath)) {
await fs.remove(tempFilePath);
}
}
}


Expand Down Expand Up @@ -274,23 +325,12 @@ export default async function configurationLoader(filePath: string, contents: st
if (!pkgDir) throw new Error(`${log.prefix('configurationLoader')} Unable to compute package directory.`);


/**
* Strategy 0: Use Vite's configuration loader.
*/
try {
const config = await withViteLoader(pkgDir, filePath);
log.verbose(log.prefix('configurationLoader'), 'Used strategy:', log.chalk.bold('vite'));
return config.default ?? config;
} catch (err: any) {
errors.push(new Error(`${log.prefix('configurationLoader')} Failed to load configuration with ${log.chalk.bold('Vite')}: ${err}`));
}

/**
* Strategy 1: Vanilla Dynamic Import
*
* This will not perform any transpilation on code, and if the host project
* uses any custom path mappings, they will not work here. This strategy is
* the simplest, however, so we try it first.
* the simplest and fastest, however, so we try it first.
*/
try {
const config = await import(filePath);
Expand All @@ -302,7 +342,20 @@ export default async function configurationLoader(filePath: string, contents: st


/**
* Strategy 2: ts-import
* Strategy 2: esbuild
*/
try {
const config = await withEsbuild(filePath);
log.verbose(log.prefix('configurationLoader'), 'Used strategy:', log.chalk.bold('esbuild'));
return config.default ?? config;
} catch (err: any) {
log.error(log.prefix('esbuild'), err);
errors.push(new Error(`${log.prefix('configurationLoader')} Failed to load configuration with ${log.chalk.bold('esbuild')}: ${err}`));
}


/**
* Strategy 3: ts-import
*/
try {
const config = await withTsImport(filePath);
Expand All @@ -314,7 +367,7 @@ export default async function configurationLoader(filePath: string, contents: st


/**
* Strategy 3: ts-node
* Strategy 4: ts-node
*
* This strategy is in place in the event that @babel/register did not work
* for some reason, but it is not ideal for reasons explained above.
Expand All @@ -330,7 +383,7 @@ export default async function configurationLoader(filePath: string, contents: st


/**
* Strategy 4: @babel/register
* Strategy 5: @babel/register
*
* This strategy uses a custom loader that uses @babel/register to transpile
* code. The loader will work with TypeScript configuration files, and it will
Expand Down
1 change: 0 additions & 1 deletion src/lib/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export default async function loadConfiguration<C>(options: SaffronCosmiconfigOp
}

if (configResult) {

return configResult as SaffronCosmiconfigResult<C>;
}
}
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"extends": "@darkobits/ts/tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
Expand Down

0 comments on commit ebfa728

Please sign in to comment.