SWC-COMMAND
SWC nodejs api to run swc cli seamlessly from node. Intended for main usage with programmatic build scripts.
Many times we find ourselves needing to write a build script instead of just configuration.
For example, combining dev build with launching electron ....
Some also like that, for the flexibility of the programmatic side.
Whatever it is. This package and module is intended for this usage. It allow you to run swc-cli
through a simple and concise api. Typed with typescript. Autocompletion. And the package provide 3 versions swc
promise version, and the version you would mostly use. cpswc
child process version , swcSync
a synchronous version.
Installation
pnpm add swc-command -D
npm install swc-command -D
yarn add swc-command -D
Usage
Let's suppose you are about to build a project.
swc()
Configuration
As by the following example. The configuration take the cli arguments as camel case equiv (--out-dir
=> outDir
).
Check the list of all arguments in the official doc
Along that we added spawnOptions
prop to configure the spawn properties.
import { swc, isCompilationSuccessful } from 'swc-command'
(async () => {
// Configuration
const cliOptions = {
outDir: path.join(rootDir, 'dist'),
src: path.join(rootDir, 'src'),
noSwcrc: true,
spawnOptions: {
encoding: 'utf8'
},
config: {
jsc: {
parser: {
decorators: true,
syntax: 'typescript',
dynamicImport: true
},
externalHelpers: true,
target: 'es5'
},
minify: true,
module: {
type: 'commonjs'
},
sourceMaps: true
}
};
// Executing the cli
const { data, exitCode, exitSignal } = await swc(cliOptions)
if (isCompilationSuccessful(data.stdout)) {
// Compilation successful
console.log(data.stdout);
startElectron();
}
// testing stdout content directly
if (data.stdout.join('').includes('Successfully compiled:')) {
// this is equivalent to the above. You can check in a similar manner for any specific case and cli output message.
}
})()
With child process instance access
You can use that to print back to console. Or in cases of output-ing the compiled source to console. And you want to process the output in a stream based.
import { swc, isCompilationSuccessful } from 'swc-command'
const stdoutData: string[] = []
(async () => {
await swc(cliOptions, (childProcess) => {
// do something with the ChildProcess instance
// check cpswc for an example
childProcess.stdout.on('data', console.log) // output to console
childProcess.stderr.on('data', console.log) //
childProcess.stdout.on('data', (data) => {
console.log(data);
// You can do whatever you want. Including printing back to the console.
})
})
})()
Cli Options
export type TPswcExecOptions = TExecOptions & {
resolveEvent?: 'close' | 'exit';
data?: {
stdout?: boolean;
stderr?: boolean;
};
};
export type TExecOptions = TCommandOptions & {
spawnOptions?: TExecSpawnOptions;
};
// TCommandOptions the cli camel case equiv args (props)
resolveEvents
resolveEvent?: 'close' | 'exit';
close
or exit
, default to close
.
And it's used to precise at what event of the spawn process should the promise resolve. It can be the close
event or the exit event
. exit
event happens before the close
event. You can read more about that in the spawn documentation.
data
data?: {
stdout?: boolean;
stderr?: boolean;
};
Optional.
Default to { stdout: true, stderr: true }
.
If any set to false. The according output wouldn't be included in the data
prop of the result object.
const { data, exitCode, exitSignal } = await swc(cliOptions);
data.stdout // will contain stdout output as an array. Undefined if not included.
data.stderr // will contain stderr output as an array. Undefined if not included.
Spawn options
It follow Nodejs child_process spawn option type
Plus we added encoding property.
export type TExecSpawnOptions = SpawnOptions & {
encoding?: BufferEncoding;
};
Default value for encoding is utf8
. You don't need to include it.
const cliOptions = {
// ...
spawnOptions: {
encoding: 'utf8'
},
// ...
};
And stdio
default to pipe
.
swcSync() [Sync version]
const {
output, // array // total output mixing both stdout and stderr and in an array format. Use join('') to process it as a string.
stdout, // string or buffer (default: string) //
stderr, // string or buffer (default: string) // depends on encoding
signal,
status // exit code
} = swcSync(cliOptions);
result output is of this type:
interface SpawnSyncReturns<T> {
pid: number;
output: Array<T | null>;
stdout: T;
stderr: T;
status: number | null;
signal: NodeJS.Signals | null;
error?: Error | undefined;
}
Worth noting for the cliOptions
. spawnOptions
is the same. And encoding
default to 'utf8'
. And stdio
to 'pipe'
.
cpswc() [child process version]
You may want to use this version if you don't want to buffer. In a wide repo. Where you compile and output to console. And you use that to process it on a stream based nature.
import { cpswc, isCompilationSuccessful } from 'swc-command'
const childProcess = cpswc(cliOptions)
.on('close', (code, signal) => {
// command finished executing.
startElectron();
})
.on('error', (/* err */) => {
// some unexpected execution for the spawning process
});
childProcess.stdout?.on('data', (data) => {
// process chunks on the go
});
childProcess.stderr?.on('data', (data) => {
// some std error data
});
CliOptions and properties Warning
Make sure you pass an object that hold the exact supported properties of the cli.
If any one is missing from the typescript declarations. Please fill a pull request, or issue to add it.
And for flexibility reason. You can add any prop. Even if it's not in the typescript declarations. And because of that choice. We do have a requirement!
Requirement:
-
The cli options object. Should contain only the cli args props. And the one that are part of the typescript declaration.
So if you constructed an object that contains all the necessary props for the cli execution. Furthermore you add some extra no cli props. You need to extract that out. Otherwise the command would fail. As it will take the prop as a cli arg. And that would make the cli to fail.
utils
isCompilationSuccessful
isCompilationSuccessful
is a helper that take stdout
output and check for Successfully compiled
as per this return _stdout.join('').includes('Successfully compiled');
which is expected when the compilation is successful.
It's a helper to simplify that. If for some reason that change. Or your use case is different. That take that in mind. You may need to figure out the way you want to check with.
You can fill a pull request, or issue if that ever change in the future.
If you need something else. You can recover data.stdout
and use return _stdout.join('').includes('Something to check with');
or anything else.
const { data, exitCode, exitSignal } = await swc(cliOptions)
if (isCompilationSuccessful(data.stdout)) {
// Compilation successful
console.log(data.stdout);
startElectron();
}
@swc/core
You can compile files programmatically using official @swc/core
package. The api only allow to manage one file at a time. And if you combine it with fast-glob
. Then you'll be able to compile multiple files and using globs as well.
swc-command
on the other hand. Would always give you the ability to run the cli. With all it's features from node seamlessly. And that may be more powerful. And what you would want to do most of the time.
Notes
Make sure you install swc-command@v1.0.3
at least. Because it does update @swc/core version to 1.3.11
. To fix the problem bellow:
Prior v1.3.5
the cli exclude argument wasn't working.
The fix i wrote was merged on 1.3.5
of @swc/core.
For more details: