easy-template-x
Generate docx documents from templates, in Node or in the browser.
Node Example
import * as fs from 'fs';
import { TemplateHandler } from 'easy-template-x';
const templateFile = fs.readFileSync('myTemplate.docx');
const data = {
posts: [
{ author: 'Alon Bar', text: 'Very important\ntext here!' },
{ author: 'Alon Bar', text: 'Forgot to mention that...' }
]
};
const handler = new TemplateHandler();
const doc = await handler.process(templateFile, data);
fs.writeFileSync('myTemplate - output.docx', doc);
Input:
Output:
Browser Example
The following example produces the same output while running in the browser.
Notice that the actual template processing (step 2) is exactly the same as in the previous Node example.
import { TemplateHandler } from 'easy-template-x';
const response = await fetch('http://somewhere.com/myTemplate.docx');
const templateFile = await response.blob();
const data = {
posts: [
{ author: 'Alon Bar', text: 'Very important\ntext here!' },
{ author: 'Alon Bar', text: 'Forgot to mention that...' }
]
};
const handler = new TemplateHandler();
const doc = await handler.process(templateFile, data);
saveFile('myTemplate - output.docx', doc);
function saveFile(filename, blob) {
const blobUrl = URL.createObjectURL(blob);
let link = document.createElement("a");
link.download = filename;
link.href = blobUrl;
document.body.appendChild(link);
link.click();
setTimeout(() => {
link.remove();
window.URL.revokeObjectURL(blobUrl);
link = null;
}, 0);
}
Live Demo
Checkout this live demo on CodeSandbox 😎
Plugins
easy-template-x
uses a plugin model to support it's various template manipulation capabilities. There are some built-in plugins and you can also write your own custom plugins if required.
Default plugins
These are the plugins that comes bundled with easy-template-x
:
Text plugin
The most basic plugin. Replaces a single tag with custom text. Preserves the original text style.
Input template:
Input data:
{
"First Tag": "Quis et ducimus voluptatum\nipsam id.",
"Second Tag": "Dolorem sit voluptas magni dolorem molestias."
}
Output document:
Loop plugin
Iterates text, table rows and lists.
Requires an opening tag that starts with #
and a closing tag that has the same
name and starts with /
.
Input template:
Input data:
{
"Beers": [
{ "Brand": "Carlsberg", "Price": 1 },
{ "Brand": "Leaf Blonde", "Price": 2 },
{ "Brand": "Weihenstephan", "Price": 1.5 }
]
}
Output document:
Image plugin
Embed images into the document.
Input template:
Input data:
{
"Kung Fu Hero": {
_type: "image",
source: fs.readFileSync("hero.png"),
format: MimeType.Png,
width: 200,
height: 200
}
}
Output document:
Link plugin
Inserts hyperlinks into the document.
Like text tags link tags also preserve their original style.
Input template:
Input data:
{
"easy": {
_type: 'link',
text: 'super easy',
target: 'https://github.com/alonrbar/easy-template-x'
}
}
Output document:
Raw xml plugin
Add custom xml into the document to be interpreted by Word.
Tip:
You can add page breaks using this plugin and the following xml markup:
<w:br w:type="page"/>
Input template:
Input data:
{
"Dont worry be happy": {
_type: 'rawXml',
xml: '<w:sym w:font="Wingdings" w:char="F04A"/>'
}
}
Output document:
Writing your own plugins
To write a plugin inherit from the TemplatePlugin class.
The base class provides two methods you can implement and a set of utilities to
make it easier to do the actual xml modification.
To better understand the internal structure of Word documents check out this excellent source.
Example plugin implementation (source):
export class RawXmlPlugin extends TemplatePlugin {
public readonly contentType = 'rawXml';
public simpleTagReplacements(tag: Tag, data: ScopeData): void {
const wordTextNode = this.utilities.docxParser.containingTextNode(tag.xmlTextNode);
const value = data.getScopeData() as RawXmlContent;
if (value && typeof value.xml === 'string') {
const newNode = this.utilities.xmlParser.parse(value.xml);
XmlNode.insertBefore(newNode, wordTextNode);
}
XmlNode.remove(wordTextNode);
}
}
The content type that this plugin expects to see is:
export interface RawXmlContent extends PluginContent {
_type: 'rawXml';
xml: string;
}
Extensions
Although most document manipulation can be achieved by using plugins, there are some cases where a more powerful tool is required. In order to extend the document manipulation process you can specify extensions that will be run before and/or after the standard template processing.
Some cases where an extension may be a good fit:
- Manipulating the document metadata (author, keywords, description, etc.).
- Adding and manipulating Content Controls.
- Leveraging Data Binding (where the data is stored in files other than the base Word files).
To write an extension inherit from the TemplateExtension class.
By default no extension is loaded.
Extensions and the order they run in are specified via the TemplateHandlerOptions
.
const handler = new TemplateHandler(
new TemplateHandlerOptions({
extensions: {
afterCompilation: [
new ContentControlExtension(),
new DataBindingExtension()
]
}
});
);
Scope resolution
easy-template-x
supports tag data scoping. That is, you can reference
"shallow" data from within deeper in the hierarchy similarly to how you can
reference an outer scope variables from within a function in JavaScript. You can
leverage this property to declare "top level" data (your logo and company name
or some useful xml snippets like page breaks, etc.) to be used anywhere in the
document.
Input template:
(notice that we are using the "Company" tag inside the "Employees" loop)
Input data:
(notice that the "Company" data is declared outside the "Employees" loop, in it's so
called "outer scope")
{
"Company": "Contoso Ltd.",
"Employees": [
{ "Surname": "Gates", "Given name": "William" },
{ "Surname": "Nadella", "Given name": "Satya" },
]
}
Output document:
Note - Advanced API
You'll usually just use the TemplateHandler
as seen in the examples but if you
want to implement a custom plugin or otherwise do some advanced work yourself
checkout the typings file. Do note however that while the
advanced API is mostly documented in the typings file it's still considered an
internal implementation detail and may break between minor versions, use at your
own risk.
Supported Binary Formats
The library supports the following binary formats:
Philosophy
The main principal the package aspire to adhere to is being simple and easy.
It tries to keep it simple and has the following priorities in mind:
- Easy for the end user who writes the templates.
- Easy for the developer who process them using the exposed APIs.
- Easy for the maintainer/contributor who maintain the
easy-template-x
package itself.
Prior art and motivation
There are already some very good templating libraries out there, most notably these two:
easy-template-x
takes great inspiration from both. It aspires to take the best
out of both and to add some good of it's own.
While these packages has some great features such as GraphQL and inline
JavaScript support in docx-templates
and a breadth of additional (payed)
modules in docxtemplater
. This package, in accordance with it's declared above
philosophy, offers some unique benefits including a most simple, non-programmer
oriented template syntax, an even neater API (IMHO 😄), a free image insertion
plugin and a TypeScript code base. Hopefully it will serve you well :)
Changelog
The change log can be found here.