├── .gitignore ├── composer.json ├── package.json ├── scripts └── install.js ├── index.js ├── php ├── lib │ └── AliasLoader.php └── Twig.php ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Suggested excludes 2 | .git 3 | CVS 4 | .svn 5 | .hg 6 | .lock-wscript 7 | .wafpickle-N 8 | *.swp 9 | .DS_Store 10 | ._* 11 | npm-debug.log 12 | 13 | # Exclude all auto-generated files 14 | .idea 15 | node_modules 16 | php/vendor 17 | composer.lock -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitmade/node-twig", 3 | "config": { 4 | "vendor-dir": "php/vendor" 5 | }, 6 | "require": { 7 | "twig/twig": "^1.24" 8 | }, 9 | "autoload": { 10 | "classmap": ["php/lib/"] 11 | }, 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Manuel Moritz-Schliesing", 16 | "email": "manuel@bitmade.de" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-twig", 3 | "description": "A small Node.js module that lets you render Twig templates using the original PHP implementation.", 4 | "keywords": [ 5 | "twig", 6 | "express", 7 | "template" 8 | ], 9 | "version": "1.2.0", 10 | "main": "index.js", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/bitmade/node-twig.git" 14 | }, 15 | "bugs": "https://github.com/bitmade/node-twig/issues", 16 | "author": "Manuel Moritz-Schliesing (http://bitmade.de)", 17 | "license": "MIT", 18 | "dependencies": { 19 | "colors": "^1.1.2", 20 | "exec-php": "^0.0.4" 21 | }, 22 | "scripts": { 23 | "install": "node scripts/install.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/install.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var colors = require('colors'); 3 | 4 | // Download Composer 5 | console.log('Downloading local Composer...'.yellow); 6 | exec('php -r "readfile(\'https://getcomposer.org/installer\');" | php', function (error, stdout) { 7 | console.log('Finished downloading local Composer!'.green); 8 | console.log('Installing Composer dependencies... (this may take a while)'.yellow); 9 | exec('php ./composer.phar install --no-dev', function (error, stdout) { 10 | console.log('Finished installing Composer dependencies!'.green); 11 | console.log('Deleting local Composer...'.yellow); 12 | exec('rm -f ./composer.phar', function () { 13 | console.log('Finished deleting local Composer!'.green); 14 | }); 15 | }); 16 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var execPHP = require('exec-php'); 2 | 3 | var twigOptions = { 4 | root: null, 5 | extensions: [], 6 | context: {} 7 | }; 8 | 9 | exports.renderFile = function(entry, options, cb) { 10 | // Merge the global options with the local ones. 11 | options = Object.assign({}, twigOptions, options); 12 | 13 | execPHP('php/Twig.php', null, function (error, php) { 14 | // Call the callback on error or the render function on success. 15 | error ? cb(error) : php.render(entry, options, function (error, stdout) { 16 | // Call the callback with an error or the trimmed output. 17 | error ? cb(error) : cb(null, stdout.trim()); 18 | }); 19 | }); 20 | }; 21 | 22 | exports.createEngine = function (options) { 23 | // Merge the options with default options. 24 | twigOptions = Object.assign(twigOptions, options); 25 | 26 | return exports.renderFile; 27 | }; 28 | 29 | exports.__express = exports.renderFile; 30 | -------------------------------------------------------------------------------- /php/lib/AliasLoader.php: -------------------------------------------------------------------------------- 1 | $path) { 22 | $this->addPath($path, $alias); 23 | } 24 | } 25 | 26 | /** 27 | * Adds a path where templates are stored. 28 | * 29 | * @param string $path 30 | * A path where to look for templates. 31 | * @param string $namespace 32 | * (optional) A path name. 33 | */ 34 | public function addPath($path, $namespace = self::MAIN_NAMESPACE) { 35 | // Invalidate the cache. 36 | $this->cache = array(); 37 | $this->paths[$namespace][] = rtrim($path, '/\\'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bitmade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-twig 2 | A small Node.js module that lets you render Twig templates using the original PHP implementation. 3 | The package also supports Express.js so this can be used as a template engine. 4 | 5 | There are a already some packages that render Twig templates but all use alternative implementations. The problem is that at the time of writing no other library seems to have all features the original PHP implementation has. That's the reason why we created this package. 6 | 7 | ## Install 8 | Install this package through NPM. 9 | ``` 10 | npm install node-twig --save 11 | ``` 12 | ## Usage 13 | ### Standalone 14 | ```javascript 15 | var renderFile = require('node-twig').renderFile; 16 | 17 | renderFile('/full/path/to/template.twig', options, function (error, template) { 18 | // ... do something with the rendered template. :) 19 | }); 20 | ``` 21 | 22 | or with ES6/ES7 23 | 24 | ```javascript 25 | import { renderFile } from 'node-twig'; 26 | 27 | renderFile('/full/path/to/template.twig', options, (error, template) => { 28 | // ... do something with the rendered template. :) 29 | }); 30 | ``` 31 | 32 | ## Express 33 | ```javascript 34 | var express = require('express'); 35 | var app = express(); 36 | var createEngine = require('node-twig').createEngine; 37 | 38 | // See available options below this example. 39 | app.engine('.twig', createEngine({ 40 | root: __dirname + '/views', 41 | })); 42 | 43 | app.set('views', './views'); 44 | app.set('view engine', 'twig'); 45 | 46 | app.get('/', function (req, res) { 47 | // The second argument is basically the same options 48 | // object like above. Most of the time you will be passing 49 | // context data that will be available in the template. 50 | res.render('index', { 51 | context: { 52 | foo: 'bar', 53 | stuff: ['This', 'can', 'be', 'anything'] 54 | } 55 | }); 56 | }); 57 | ``` 58 | 59 | ## Options 60 | The options object can be passed to the `renderFile()` function to configure the Twig engine. 61 | For convinience the `createEngine()` function can also consume the options which will then be the global defaults for the runtime. This is useful for global settings such as the root path, but not so useful for context information. 62 | 63 | ### root 64 | default: `null` 65 | The *absolute* path to the main template directory. In Express this is probably the same as the value you set for `views` in `app.set('views', './your-root')` but please use an absolute path. 66 | 67 | ### context 68 | default: `{}` 69 | The value of the context option will be available inside the Twig template. You can use scalar values, arrays or objects at any depths. 70 | 71 | ### extensions 72 | default: `[]` 73 | Since Twig is a PHP library there is no (easy) way to make it extandable inside Node. That's the reason why we provide you with the `extensions` option. 74 | 75 | An extension is just a function that takes a reference (*Always use the `&` sign for the parameter*) to the Twig environment which can then be used to define custom functions or filters. To allow for greater flexibility you can add multiple files. 76 | 77 | Define your extensions like so: 78 | 79 | ```javascript 80 | var options = { 81 | extensions: [ 82 | { 83 | file: '/absolute/path/to/php/file.php', 84 | func: 'myTwigExtension' 85 | } 86 | ] 87 | }; 88 | ``` 89 | 90 | Your PHP file could look like this: 91 | 92 | ```php 93 | addFunction('url', new \Twig_SimpleFunction('url', function ($context, $destination) { 97 | return 'something-fancy'; 98 | })); 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /php/Twig.php: -------------------------------------------------------------------------------- 1 | array(), 68 | 'aliases' => array(), 69 | 'context' => array(), 70 | ), $options); 71 | 72 | // Get the root template directory either from the given file or specified in the options. 73 | $rootDir = array_key_exists('root', $options) && $options['root'] 74 | ? $options['root'] : $fileInfo['dirname']; 75 | 76 | $prefix = _getFilepathPrefix($rootDir, $fileInfo['dirname']); 77 | 78 | $loader = new Twig_Loader_Chain(array( 79 | new AliasLoader(array(), $options['aliases']), 80 | new Twig_Loader_Filesystem($rootDir), 81 | )); 82 | 83 | // @todo Provide a mechanism to allow custom Twig extensions either via PHP or better JS. 84 | $twig = new Twig_Environment($loader); 85 | 86 | _invokeExtensions($options['extensions'] ?: array(), $twig); 87 | 88 | try { 89 | return $twig->render($prefix . $fileInfo['basename'], $options['context']); 90 | } 91 | catch (\Exception $e) { 92 | return _createPrettyError($e->getMessage()); 93 | } 94 | } 95 | 96 | /** 97 | * Creates a pretty looking page that displays the error message. 98 | * 99 | * @param string $message 100 | * The error message to display. 101 | * 102 | * @return string 103 | */ 104 | function _createPrettyError($message = '') { 105 | return << 107 | 108 | Twig Error 109 | 126 | 127 | 128 |
{$message} 129 | 130 | 131 | EOT; 132 | } 133 | --------------------------------------------------------------------------------