Embedding Guchho in C++
The guchho/api.hpp and guchho/cli.hpp surfaces, from linking to lifetime rules.
#Guchho is a C++ program with two public headers. The one most embedders want
is guchho/api.hpp: it is the whole engine as a library, and the shipped
guchho executable is just a small argument parser on top of it. The other,
guchho/cli.hpp, is the command line as a library - a way to run, or to
inspect, a complete command line without forking a process.
This page is the reasoning; the exact spelling of every declaration is generated from the headers themselves in the Generated embedding reference, so it can never drift.
The three surfaces, and which one to use
#guchho::api- build, transform, watch, serve. This is--outdir, the incremental context, the dev server, all of it, reachable from your process. If your program wants to bundle, this is the surface.guchho::cli- the command line as a library.Run(argc, argv)prints usage and diagnostics like the program would and returns the exit code; theParse*Optionsfunctions share the flag grammar but do no I/O, so you can find out what a command line would mean without running it.- The service - what the npm package actually runs. The binary speaks a
framed protocol on stdin/stdout, and Node turns each build request into a
response that carries outputs and diagnostics as data. You do not need this
unless you are a host process that cannot link the library; it is spelled
out in
RunServiceand the service calls in the generated reference.
Compiling and linking
#The library is C++20 (cxx_std_20). Add the repository's include/
directory to your include path and link the static libraries:
| Library | holds | links |
|---|---|---|
guchho_api |
the whole engine: guchho::api |
guchho_common, guchho_bundler |
guchho_cli |
the command line surface: guchho::cli |
guchho_api |
On Windows the API target also pulls in ws2_32 (sockets for the dev server)
and iphlpapi (the IP Helper used to enumerate local addresses); on POSIX a
threads library is enough. The CMake targets carry all of that as PUBLIC
dependencies, so linking one target is all you need to write:
target_link_libraries(my_tool PRIVATE guchho_api)
The shape of the surface
#The API is unusually declarative in a way that makes embedding it small:
- Vocabulary is scoped enums.
Loader,Format,Platform,SourceMap,TreeShaking,LogLevel- every decision a build can make is an enumerator, and option structs reference them by name, so a build described in code reads like a build described on the command line. - Configuration is input-only plain structs. Every member of
BuildOptionsandTransformOptionshas a default. You name the fields you care about and leave the rest alone; there is no builder, no fluent chain, no copy that the engine mutates. - Failure is data, not exceptions. A missing entry point is not thrown at
you: it arrives as a
MessageinBuildResult.errors, with aLocationpointing at the offending path, and the diagnostics render withFormatMessagesif you want the printed form. Exceptions are reserved for programming errors and for things like callingServeon a disposed context.
A first build
##include <guchho/api.hpp>
using namespace guchho::api;
int main() {
BuildOptions opts;
opts.entry_points = { "src/index.js" };
opts.bundle = true;
opts.format = Format::kESModule;
opts.write = true;
opts.outdir = "dist";
BuildResult result = Build(opts);
if (!result.errors.empty()) {
// result.errors[i] is a Message; FormatMessages renders them.
return 1;
}
return 0;
}
That is the whole one-shot path: resolve, walk the graph, compile, print. It
tears the session down again afterwards. When a caller intends to build more
than once, Context(), BuildContext::Rebuild() and BuildContext::Dispose()
do the same work while keeping the caches alive and letting a watcher or a
server own the rebuilds - which is also the shape that can be cancelled. See
BuildContext in the generated reference.
Lifetime rules
#The surface has a small set of rules that hold everywhere:
- Nothing is started implicitly. A watcher, a server, a context - you create
it, you destroy it.
Watcher::Start()andWatcher::Stop()are explicit. - A disposed
BuildContextis inert, not dangerous: its methods return empty results or astd::runtime_errorrather than crashing a late caller from another thread. - Concurrent
Rebuild()calls are fine - the second caller waits and receives the same result instead of starting a competing build. - Work is synchronous unless an object owns it. There is no hidden thread pool doing jobs you did not ask for.
Read the full declarations in the
Generated embedding reference - every type and function
is transcribed from include/guchho/api.hpp and include/guchho/cli.hpp, so
what you look up is what the headers say.