├── .gitignore ├── package.json ├── LICENSE ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore imported dependencies 2 | node_modules 3 | 4 | # Ignore git stuff 5 | .git 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ejs-electron", 3 | "version": "3.0.0", 4 | "description": "A lightweight module for ejs templating in an electronJS app.", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "dependencies": { 8 | "ejs": "^3.1.9", 9 | "mime": "^3.0.0" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/bowheart/ejs-electron/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/bowheart/ejs-electron" 17 | }, 18 | "keywords": [ 19 | "electron", 20 | "ejs" 21 | ], 22 | "author": "Joshua Claunch", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Joshua Claunch (bowheart) 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Define the module 4 | const EjsElectron = (module.exports = { 5 | data, 6 | listen, 7 | listening, 8 | options, 9 | stopListening, 10 | }) 11 | 12 | // Load dependencies 13 | const { app, protocol } = require('electron') 14 | const ejs = require('ejs') 15 | const fs = require('fs') 16 | const mime = require('mime') 17 | const path = require('path') 18 | 19 | // Set up local state 20 | let state = { 21 | data: {}, 22 | listening: false, 23 | options: {}, 24 | } 25 | 26 | // Method API 27 | 28 | /** 29 | * EjsElectron.data() -- Get/set the data (context) that will be passed to `ejs.render()`. 30 | * Overloads: 31 | * 32 | * - ejse.data('key') -- Retrieve the value of 'key' in the current data set. 33 | * - ejse.data('key', 'val') -- Set 'key' to 'val' in the current data set. 34 | * - ejse.data({key: 'val'}) -- Replace the current data set with a new one containing {key: 'val'} 35 | */ 36 | function data(key, val) { 37 | return updateState('data', 'ejse.data()', key, val) 38 | } 39 | 40 | /** 41 | * EjsElectron.listen() -- Start intercepting requests on the 'file:' protocol, looking for '.ejs' files. 42 | * It is not necessary to call this function up-front, as ejs-electron starts listening as soon as it's loaded. 43 | * Use this only to start listening again after calling EjsElectron.stopListening(). 44 | */ 45 | function listen() { 46 | if (state.listening) return EjsElectron // already listening; nothing to do here 47 | 48 | protocol.handle('file', protocolListener) 49 | state.listening = true 50 | return EjsElectron // for chaining 51 | } 52 | 53 | /** 54 | * EjsElectron.listening() -- True if ejs-electron is currently intercepting requests on the 'file:' protocol. 55 | */ 56 | function listening() { 57 | return state.listening 58 | } 59 | 60 | /** 61 | * EjsElectron.options() -- Get/set the options that will be passed to `ejs.render()`. 62 | * Overloads: 63 | * ejse.options('key') -- Retrieve the value of 'key' in the current options set. 64 | * ejse.options('key', 'val') -- Set 'key' to 'val' in the current options set. 65 | * ejse.options({key: 'val'}) -- Replace the current options set with a new one containing {key: 'val'} 66 | */ 67 | function options(key, val) { 68 | return updateState('options', 'ejse.options()', key, val) 69 | } 70 | 71 | /** 72 | * EjsElectron.stopListening() -- Stop intercepting requests, restoring the original 'file:' protocol handler. 73 | */ 74 | function stopListening() { 75 | if (!state.listening) return EjsElectron // we're not listening; nothing to stop here 76 | 77 | protocol.unhandle('file') 78 | state.listening = false 79 | 80 | return EjsElectron 81 | } 82 | 83 | // Helper Functions 84 | function compileEjs(pathname, contentBuffer) { 85 | state.data.ejse = EjsElectron 86 | state.options.filename = pathname 87 | 88 | let contentString = contentBuffer.toString() 89 | let compiledEjs = ejs.render(contentString, state.data, state.options) 90 | 91 | return new Buffer.from(compiledEjs) 92 | } 93 | 94 | function parsePathname(reqUrl) { 95 | let parsedUrl = new URL(reqUrl) 96 | let pathname = decodeURIComponent(parsedUrl.pathname) 97 | 98 | if (process.platform === 'win32' && !parsedUrl.host.trim()) { 99 | pathname = pathname.substring(1) 100 | } 101 | 102 | return pathname 103 | } 104 | 105 | function protocolListener(request) { 106 | try { 107 | let pathname = parsePathname(request.url) 108 | let fileContents = fs.readFileSync(pathname) 109 | let extension = path.extname(pathname) 110 | let mimeType = mime.getType(extension) 111 | 112 | if (extension === '.ejs') { 113 | fileContents = compileEjs(pathname, fileContents) 114 | mimeType = 'text/html' 115 | } 116 | 117 | return new Response(fileContents, { 118 | headers: { 'Content-Type': mimeType }, 119 | }) 120 | } catch (exception) { 121 | console.error(exception) 122 | 123 | if (exception.code === 'ENOENT') { 124 | // HTTP Error 404 - Not Found 125 | return new Response(null, { status: 404, statusText: 'Not Found' }) 126 | } 127 | 128 | // Internal error e.g. ejs compilation error 129 | return new Response(null, { status: 500 }) 130 | } 131 | } 132 | 133 | function updateState(field, context, key, val) { 134 | if (typeof key === 'string') { 135 | if (typeof val === 'undefined') return state[field][key] 136 | 137 | state[field][key] = val 138 | 139 | return EjsElectron // for chaining 140 | } 141 | 142 | if (Array.isArray(key) || typeof key !== 'object') { 143 | throw new TypeError( 144 | `EjsElectron Error - ${context} - Method accepts either a key and (optional) value or an object. Received ${typeof key}` 145 | ) 146 | } 147 | 148 | state[field] = key 149 | 150 | return EjsElectron // for chaining 151 | } 152 | 153 | // Get this party started 154 | app.on('ready', listen) 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ejs-electron 2 | 3 | [![npm](https://img.shields.io/npm/v/ejs-electron.svg)](https://www.npmjs.com/package/ejs-electron) 4 | [![npm](https://img.shields.io/npm/dt/ejs-electron.svg)](https://www.npmjs.com/package/ejs-electron) 5 | 6 | A mega lightweight, completely flexible module that allows ejs templating in an Electron app. 7 | 8 | Makes use of the Electron `protocol` module to supply a custom handler for the `file:` protocol. This handler intercepts all file requests, compiles any requested `.ejs` files, and serves the result. 9 | 10 | --- 11 | 12 | ## Installation 13 | 14 | Install using [npm](https://www.npmjs.com/package/ejs-electron): 15 | 16 | ``` 17 | $ npm install ejs-electron 18 | ``` 19 | 20 | --- 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | const ejse = require('ejs-electron') 26 | ``` 27 | 28 | --- 29 | 30 | ### Method API 31 | 32 | > Note: All methods, unless otherwise specified, return the `ejs-electron` api for chaining. 33 | 34 | #### ejse.data() 35 | 36 | Get/set the data (context) that will be passed to `ejs.render()`. 37 | 38 | Overloads: 39 | - `ejse.data('key')` -- Retrieve the value of `'key'` in the current data set. 40 | - `ejse.data('key', 'val')` -- Set `'key'` to `'val' `in the current data set. 41 | - `ejse.data({key: 'val'})` -- Replace the current data set with a new one containing `{key: 'val'}` 42 | 43 | > Note: The `ejs-electron` api is injected into the scope of all rendered ejs templates. Access it via the variable `ejse`, e.g. `<% ejse.stopListening() %>`. 44 | 45 | #### ejse.options() 46 | 47 | Get/set the options that will be passed to `ejs.render()`. These configure the behavior of ejs itself. See the [ejs docs](http://ejs.co/#docs) for a list of possible options. 48 | 49 | Overloads: 50 | - `ejse.options('key')` -- Retrieve the value of `'key'` in the current options set. 51 | - `ejse.options('key', 'val')` -- Set `'key'` to `'val' `in the current options set. 52 | - `ejse.options({key: 'val'})` -- Replace the current options set with a new one containing `{key: 'val'}` 53 | 54 | > Note: `ejs-electron` sets the ejs `filename` option automatically every time it renders a file. This means you can go ahead and use ejs `include` right out of the box. One less thing you need to worry about :) 55 | 56 | #### ejse.listen() 57 | 58 | Start intercepting requests on the 'file:' protocol, looking for '.ejs' files. 59 | 60 | > Note: It is not necessary to call this function up-front, as `ejs-electron` starts listening as soon as it's loaded. 61 | Use this only to start listening again after calling `ejse.stopListening()`. 62 | 63 | #### ejse.listening() 64 | 65 | Returns true if `ejs-electron` is currently intercepting requests on the `file:` protocol. 66 | 67 | #### ejse.stopListening() 68 | 69 | Stop intercepting file requests, restoring the original `file:` protocol handler. 70 | 71 | ___ 72 | 73 | ## Examples 74 | 75 | A simple Electron app with `ejs-electron` could look like this: 76 | 77 | ##### main.js 78 | 79 | ```javascript 80 | const {app, BrowserWindow} = require('electron') 81 | const ejse = require('ejs-electron') 82 | 83 | let mainWindow 84 | 85 | ejse.data('username', 'Some Guy') 86 | 87 | app.on('ready', () => { 88 | mainWindow = new BrowserWindow() 89 | mainWindow.loadURL('file://' + __dirname + '/index.ejs') 90 | }) 91 | ``` 92 | 93 | You can, of course, chain `data()`, `options()`, and whatnot to the `require()` call: 94 | 95 | ```javascript 96 | const ejse = require('ejs-electron') 97 | .data('username', 'Some Guy') 98 | .options('debug', true) 99 | ``` 100 | 101 | ##### index.ejs 102 | 103 | ```html 104 |

Hello, <%= username %>

105 | <% ejse.stopListening() %> 106 | ``` 107 | 108 | Since you have access to the `ejs-electron` api in your templates, you can also use the getter overload of `ejse.data()` to access the root-level scope of your templates. This can be useful for providing constancy in nested ejs includes: 109 | 110 | ##### main.js 111 | ```javascript 112 | ejse.data('name', 'Holmes') 113 | ``` 114 | 115 | ##### profile.ejs 116 | ```html 117 |

Your name: <%= name %>

118 | <%- include('./dog', {name: 'Sparky'}) %> 119 | ``` 120 | 121 | ##### dog.ejs 122 | ```html 123 |

The dog's name: <%= name %>

124 |

This dog belongs to: <%= ejse.data('name')

125 | ``` 126 | 127 | A heavily contrived example, sure, but here's its output: 128 | 129 | ```html 130 |

Your name: Holmes

131 |

The dog's name: Sparky

132 |

This dog belongs to: Holmes

133 | ``` 134 | 135 | This also means that stuff like the following is also a possibility, though I've never yet found a use for it: 136 | 137 | ```html 138 |

The current file is: <%= ejse.options('filename') %>

139 | ``` 140 | 141 | --- 142 | 143 | ## Issues 144 | 145 | Issues may be submitted at https://github.com/bowheart/ejs-electron/issues 146 | 147 | Thanks to all who have submitted issues. The feedback has been extremely helpful (no, seriously, you guys rock). 148 | 149 | Also, of course, feel free to fork and pull request. Happy coding! 150 | 151 | --- 152 | 153 | ## License 154 | 155 | The [MIT License](LICENSE) 156 | --------------------------------------------------------------------------------