Tailor is a layout service that uses streams to compose a web page from fragment services.
Microservices get a lot of traction these days. They allow multiple teams to work independently from each other, choose their own technology stacks and establish their own release cycles. Unfortunately, frontend development hasn’t fully capitalized yet on the benefits that microservices offer. The common practice for building websites remains “the monolith”: a single frontend codebase that consumes multiple APIs.
What if we could have microservices on the frontend? This would allow frontend developers to work together with their backend counterparts on the same feature and independently deploy parts of the website — “fragments” such as Header, Product, and Footer. Bringing microservices to the frontend requires a layout service that composes a website out of fragments. Tailor was developed to solve this need.
npm i node-tailor --save
const http = require('http');
const Tailor = require('node-tailor');
const tailor = new Tailor({/* Options */});
const server = http.createServer(tailor.requestHandler);
server.listen(process.env.PORT || 8080);
fetchContext(request)
a function that returns a promise of the context, that is an object that maps fragment id to fragment url, to be able to override urls of the fragments on the page, defaults toPromise.resolve({})
fetchTemplate(request, parseTemplate)
a function that should fetch the template, callparseTemplate
and return a promise of the result. Useful to implement your own way to retrieve and cache the templates, e.g. from s3. Default implementationlib/fetch-template.js
streams the template from the file systemfragmentTag
a name of the fragment tag, defaults tofragment
handledTags
an array of custom tags, checktests/handle-tag
for more infohandleTag(request, tag)
receives a tag or closing tag and serializes it to a string or returns a streamrequestFragment(url, fragmentAttributes, request)
a function that returns a promise of request to a fragment server, check the default implementation inlib/request-fragment
amdLoaderUrl
- URL to AMD loader. We use RequireJS from cdnjs as deafult.
Tailor uses sax to parse the template, where it replaces each fragmentTag
with a stream from the fragment server and handledTags
with the result of handleTag
function.
<html>
<head>
<fragment src="http://assets.domain.com">
</head>
<body>
<fragment src="http://header.domain.com">
<fragment src="http://content.domain.com" primary>
<fragment src="http://footer.domain.com" async>
</body>
</html>
- id — optional unique identifier (autogenerated)
- src — URL of the fragment
- primary — denotes a fragment that sets the response code of the page
- timeout - optional timeout of fragment in milliseconds (default is 3000)
- async — postpones the fragment until the end of body tag
- public — doesn't send the headers to fragment server
- fallback-src - URL of the fallback fragment in case of timeout/error on the current fragment
A fragment is an http(s) server that renders only the part of the page and sets Link
header to provide urls to CSS and JavaScript resources. Check example/fragment.js
for the draft implementation.
A JavaScript of the fragment is an AMD module, that exports an init
function, that will be called with DOM element of the fragment as an argument.
Tailor
extends EventEmitter
, so you can subscribe to events with tailor.on('eventName', callback)
.
Events may be used for logging and monitoring. Check perf/benchmark.js
for an example of getting metrics from Tailor.
- Client request received:
start(request)
- Response started (headers flushed and stream connected to output):
response(request, status, headers)
- Response ended (with the total size of response):
end(request, contentSize)
- Error:
error(request, error)
in case an error from template (parsing,fetching) and primary error(socket/timeout/50x) - Context Error:
context:error(request, error)
in case of an error fetching the context
- Request start:
fragment:start(request, fragment.attributes)
- Response Start when headers received:
fragment:response(request, fragment.attributes, status, headers)
- Response End (with response size):
fragment:end(request, fragment.attributes, contentSize)
- Error:
fragment:error(request, fragment.attributes, error)
in case of socket error, timeout, 50x - Fallback:
fragment:fallback(request, fragment.attributes, error)
in case of timeout/error from the fragment if the fallback-src is specified
Note: fragment:response
, fragment:fallback
and fragment:error
are mutually exclusive. fragment:end
happens only in case of successful response.
To start an example execute npm run example
and open http://localhost:8080/index.
To start running benchmark execute npm run benchmark
and wait for couple of seconds to see the results.