Bagel
🥯 Flexible rendering service for server-rendered React applications ⚛︎
Install / Use
/learn @wayfair/BagelREADME
_ _
| | | |
| |__ __ _ __ _ ___| |
| '_ \ / _` |/ _` |/ _ \ |
| |_) | (_| | (_| | __/ |
|_.__/ \__,_|\__, |\___|_|
__/ |
|___/
Bagel is a flexible rendering service for server-rendered React applications.
Bagel was built to solve a specific set of problems at Wayfair:
- React rendering as a service. If you have an existing non-JavaScript backend and want to render React applications, Bagel can help.
- Configurable network transport layer. Maybe you want to use HTTP to communicate with your service. Maybe you want to use WebSockets, maybe TCP, maybe Morse code over telegraph. Bagel makes it easy to use whatever you want.
- Extremely flexible module loading. Code built to run in a browser may not run correctly in a server environment where all modules are singletons shared across requests. Frontend code may rely on module formats (haste-style flat namespaces, for example) that don't work out-of-the-box in a Node environment. You may need to have fine-grained control over which specific modules are singletons, shared across requests, and which are reloaded every time.
Getting Started
Bagel is designed to be run as an imported library.
yarn install bagel
import bagel from 'bagel'
bagel({
moduleResolvers: [],
interceptors: [],
sourceCodeTransformers: [],
port: 3030
});
// bagel is now ready to serve requests
The code above will only be able to load modules using the default Node.js module resolver, which means you will only be able to render React components that are in your project's node_modules directory. For more flexibility with how your root components are loaded, you'll need to supply a module resolver. See the Resolver type in the module loader index file file or examples in tests for details on what a module resolver should look like.
Bagel should work with any Hypernova client. Please see tests for a good selection of examples.
Plugins
Developers can create plugins to tap into Bagel's core operation. With plugins, you are able to hook into actions before and after they happen. Plugins have access to a context object which they are able to write to and use within various stages of the request.
Flow types for plugins:
type LifeCycleMethod<T> = T => Promise<void> | void;
type Plugin = {
beforeBatch?: LifeCycleMethod<BatchHandlerRequest>,
afterBatch?: LifeCycleMethod<BatchHandlerResponse>,
beforeJob?: LifeCycleMethod<JobHandlerRequest>,
afterJob?: LifeCycleMethod<JobHandlerResponse>,
beforeLoadModule?: LifeCycleMethod<JobHandlerRequest>,
afterLoadModule?: LifeCycleMethod<RenderHandlerRequest>,
beforeRender?: LifeCycleMethod<RenderHandlerRequest>,
afterRender?: LifeCycleMethod<JobHandlerResponse>
};
Request and Response Types
A Bagel request / response cycle is encompassed in a BatchRequest and a BatchResponse. These objects, and the associated JobRequest / JobResponse objects, are the primary means by which data flows through the Bagel application during a request.
A batch is a single request / response cycle from a client application. A batch may contain one or more jobs. A job corresponds to a single root React component which needs to be rendered.
Most of the hooks provided by Bagel (plugins, module loader interceptors, module resolvers) have access to job and batch requests and response objects.
Metadata
Applications may pass request-related metadata into Bagel, where Bagel returns response-related metadata. Example uses of metadata inlude supplying a request ID to Bagel, and supplying performance profile data to a client application.
Batch Context
Each batch has its own 'global' context object which is present during the entire life cycle of the batch. We know unstructured globals are a madness-inducing antipattern, but sometimes you need an escape hatch. If you need to share arbitrary data between different parts of the system during a request, the context object will permit this.
Flow Types
type BatchRequest = {
jobs: {[string]: JobRequest},
// context is a single object, 'global' to the batch, shared between the BatchRequest and BatchReponse object
context: {[string]: any}
};
type BatchResponse = {
jobs: {[string]: JobResponse},
// context is a single object, 'global' to the batch, shared between the BatchRequest and BatchReponse object
context: {[string]: any}
};
type JobRequest = {
name: string,
props: {},
metadata: {
jobId: string,
[string]: any
}
};
type JobResponse = {
htmlStream: Readable,
metadata: {[string]: any}
};
Built With
Contributing
Please read Code of Conduct.md for details on our Code of Conduct.
To contribute, please open an issue or submit a pull request with appropriate test coverage.
Building
yarn build
Running the tests
yarn test
yarn test-watch
yarn test-watch-debug
yarn test-debug
Running eslint
yarn lint
Checking Flow Types
yarn flow
Deploying
In a production environment, you'll want to have a process manager such as PM2 to keep the correct number of Bagel workers running.
Authors
License
This project is licensed under the BSD-2-Clause license. See the License file for details.
History
Bagel began as a fork of Airbnb's Hypernova renderer (thanks Airbnb!). By default, it implments the same API as Hypernova, and can be used with any Hypernova client. Wayfair uses a version of our own publically available PHP client which has been modified to use WebSockets instead of HTTP.
We chose the name Bagel because:
- Rather than going large, hard, powerful, and industrial with the name, we opted for modest, simple, and tasty.
- Riffing on the name of Bagel's main inspiration, Hypernova, we thought about nova lox, which is a kind of smoked salmon, which tastes great on bagels.
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
