├── .babelrc
├── .editorconfig
├── .eslintrc.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── coffeelint.json
├── package-lock.json
├── package.json
├── src
├── cache.js
├── index.js
└── render.js
└── test
├── config
└── paths.coffee
├── fixtures
├── bad-locals.ejs
├── bad-locals.js
├── bad-syntax.ejs
├── bad-syntax.js
├── context.ejs
├── context.js
├── delimiter.ejs
├── delimiter.js
├── include-partial.ejs
├── include.ejs
├── include.js
├── resource-query.ejs
├── resource-query.js
├── simple.ejs
└── simple.js
├── helpers
├── compile.coffee
├── config.coffee
└── error.coffee
├── index.coffee
└── mocha.opts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {"targts": {"node": "current"}}]
4 | ],
5 | "plugins": [
6 | "add-module-exports"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends: eslint:recommended
2 | parserOptions:
3 | ecmaVersion: 6
4 | sourceType: module
5 | env:
6 | node: true
7 | mocha: true
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage/
2 | /lib/
3 | /node_modules/
4 | /npm-debug.*
5 | .tmp
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /coverage/
2 | /src/
3 | /test/
4 | /.babelrc
5 | /.editorconfig
6 | /.eslintrc.yml
7 | /.gitignore
8 | /.travis.yml
9 | /coffeelint.json
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | after_success:
5 | - npm run coveralls
6 | before_deploy:
7 | - npm run build
8 | deploy:
9 | provider: npm
10 | email:
11 | secure: E1X0Di6u1dhOXAvEsGXTTMZzz0Cx7RqNS9O4U5Tgdqlqaq9maDoHz8rZNHwqcx97m6DkgH4/go+D22DARLPr7Y+5XJNBrFcDKWPbU34lBHsTjwhxnuoKS8h7VZ/lGvQqPp1ircyYvsG5rd97J4z0hI27gF3Sj9tk8W3bn3ZTc8JxIoEw00aiEC7KNNnDpOosWkHv+SiVASo5Fk9Iwdt+k8FRtmrfmjVb/K1qp8e4lkjN8sXtxLoAYqzuOt5TUpTFDzGjlY4KS42apJxq0cKTvPXsU38Wn4QE848E4urJAIHKLR8N45wapD54FxlvS4FKjWUCpscVf8vDAGPXPbFK0uireqlI1RdHZYhEKeyR0vnIC62lQbYd9GKrh26q9TVeGniKXfIpNC/j5gp6dexYkKqNjmWE5K1CgTplgLq5ufmZArkeS+hKrSGcdGYcfib5hGM3gEfisK0Mvm0/rRposChu3UwUh9IlEblKhGbsynrs8Ee8zSerrTPnuaFuJycDO7m+SpxBe5ujf7W4/Dhh8etCsNIG0f9xxlVSfwgeeLONs1z1i94g7gGHR4n1JTW4/xLhyJuE4jsvEm23y4M1nDwEOCiPw1oJvCASXxxKN5/ydQ0wJsV6t7R0RFwDQSbOA4FYlYsWQQzmqgLJ8BES2JgAEuFhCEHpT/K4lVZl9oA=
12 | api_key:
13 | secure: YGkGJVV8Wx86+4fy8k88vLBku/JU7402q+fFS7VFLa1Lt017N9qsIasJgSiUWBsmRbp6L8FqT77yv7BZsyn3swdeFk/Uz8Le3lKQgANFAy7JQj/4MaceZsGxvuOU1+PHsFMqZDlIQpTzkaiPN6bVCj/nrxspPfTEiaDThsNPy/BNXjoPMnwvg8PuRgotJSHEx1vWlemnLql69RnfoWO0dqO1RY8To8wNjPJVQtMsOGgdDbg1Gi6FAdp3YltVbCIl9XSn0ibjo+sEPfHEhToPszlbyExm4GelocK3qCFowTE6YMO/PHNXq12oPI/mOB0VESyNeawnT6MYQYI+ClgWS8KXGaZVUuOJ3s9nkPODMjTBmx7TElPWBmNNtutOI12VxTmXKs+gKynJuEf7T9xIgunIuzDOFZR/OflT1qySQoVGeYBJCjYeG2IA0hIVcnVHmYBybVuw6ut5A1N5fORxVS+yToJRfR8IrU6dmuhd61nffoAPJ81TzOvcGIldKuN0g6wfc40EaJQr+QZgUvBPp72jffajP0DviQ3RBx0y3ktikqvetm0TuXc9FzLEkjBKJY4r2BKahTcqv9YyPI4Ui9vNvjaD2VG4Qa7HtsUNi342/uaVnRMf24/VI/UOkhgI8e2DDKH35RucLK0sxrEEUfTwqNM2jhBuNQHA+BDpyAg=
14 | skip_cleanup: true
15 | on:
16 | repo: mcmath/ejs-html-loader
17 | node: "6"
18 | tags: true
19 | after_deploy:
20 | - npm run unbuild
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2017 Akim McMath
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ejs-html-loader
2 |
3 | [![Version][version-badge]][npm]
4 | [![Build][build-badge]][travis]
5 | [![Coverage][coverage-badge]][coveralls]
6 |
7 | [Webpack][webpack] loader for rendering plain HTML from [EJS][ejs]
8 | template files
9 |
10 | ## Install
11 |
12 | Install with [npm][npm]. Ensure [EJS][ejs] and [Webpack][webpack] are installed
13 | as well, as these are peer dependencies.
14 |
15 | ```sh
16 | npm install --save-dev webpack ejs ejs-html-loader
17 | ```
18 |
19 | ## Usage
20 |
21 | In your Webpack [configuration][webpack-configuration], pass data to your
22 | templates through either an `'options'` object or as query parameters.
23 |
24 | ```js
25 | module.exports = {
26 | // ...
27 | module: {
28 | rules: [{
29 | test: /\.ejs$/,
30 | loader: 'ejs-html-loader',
31 | options: {
32 | title: 'The Ant: An Introduction',
33 | season: 1,
34 | episode: 9,
35 | production: process.env.ENV === 'production'
36 | }
37 | }]
38 | }
39 | };
40 | ```
41 |
42 | Data may also be passed through a resource query. These data take precedence
43 | over any options with the same name.
44 |
45 | ```js
46 | import "./index.ejs?page=home";
47 | ```
48 |
49 | ## Options
50 |
51 | All properties passed as loader options will be available to your
52 | templates as local variables. In addition, the following [EJS][ejs]
53 | options may be set:
54 |
55 | * **context** : `object`
56 | The value of `this` in your templates. If specified, its properties will be
57 | available in your templates, e.g. `<%= this.somePropery %>`.
58 |
59 | * **delimiter** : `string='%'`
60 | Character used inside of angle brackets marking opening/closing tags.
61 | Defaults to `'%'`, as in `<%= some.variable %>`.
62 |
63 | For example:
64 |
65 | ```js
66 | {
67 | // ...
68 | options: {
69 | delimiter: '$',
70 | title: 'The Naked Ant',
71 | season: 1,
72 | episode: 12
73 | }
74 | }
75 | ```
76 |
77 | ## Includes
78 |
79 | The EJS `filename` option is set automatically, so you may include partials
80 | relative to your template files. If you want your included files to
81 | automatically recompile in watch mode, be sure to use the following syntax:
82 |
83 | ```
84 | <% include some/file %>
85 | ```
86 |
87 | ## License
88 |
89 | Copyright © 2016–2019 Akim McMath. Licensed under the [MIT License][license].
90 |
91 | [version-badge]: https://img.shields.io/npm/v/ejs-html-loader.svg?style=flat-square
92 | [build-badge]: https://img.shields.io/travis/mcmath/ejs-html-loader/master.svg?style=flat-square
93 | [coverage-badge]: https://img.shields.io/coveralls/mcmath/ejs-html-loader/master.svg?style=flat-square&service=github
94 | [dependencies-badge]: https://img.shields.io/gemnasium/mcmath/ejs-html-loader.svg?style=flat-square
95 |
96 | [npm]: https://www.npmjs.com/package/ejs-html-loader
97 | [license]: LICENSE
98 | [travis]: https://travis-ci.org/mcmath/ejs-html-loader
99 | [coveralls]: https://coveralls.io/github/mcmath/ejs-html-loader?branch=master
100 | [gemnasium]: https://gemnasium.com/mcmath/ejs-html-loader
101 | [webpack]: https://webpack.js.org/
102 | [webpack-configuration]: https://webpack.js.org/configuration/
103 | [ejs]: http://ejs.co/
104 |
--------------------------------------------------------------------------------
/coffeelint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@mcmath/coffeelint-config"
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ejs-html-loader",
3 | "version": "4.0.1",
4 | "description": "Webpack loader for rendering HTML from EJS templates",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "build": "npm run unbuild && babel src -d lib",
8 | "unbuild": "rimraf lib",
9 | "test:lint": "eslint src test && coffeelint -q test",
10 | "test:unit": "istanbul cover _mocha -- test/index.coffee",
11 | "test:report": "npm run test:unit && open coverage/lcov-report/index.html",
12 | "test": "npm run test:lint && npm run test:unit",
13 | "coveralls": "cat coverage/lcov.info | coveralls"
14 | },
15 | "engines": {
16 | "node": ">=6"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/mcmath/ejs-html-loader.git"
21 | },
22 | "keywords": [
23 | "webpack",
24 | "loader",
25 | "webpack-loader",
26 | "ejs",
27 | "html",
28 | "template"
29 | ],
30 | "author": "Akim McMath (http://www.mcmath.io/)",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/mcmath/ejs-html-loader/issues"
34 | },
35 | "homepage": "https://github.com/mcmath/ejs-html-loader#readme",
36 | "peerDependencies": {
37 | "webpack": "2.x - 4.x",
38 | "ejs": "2.x"
39 | },
40 | "dependencies": {
41 | "loader-utils": "^1.2.3"
42 | },
43 | "devDependencies": {
44 | "@mcmath/coffeelint-config": "^1.0.1",
45 | "babel-cli": "^6.26.0",
46 | "babel-plugin-add-module-exports": "^1.0.0",
47 | "babel-preset-env": "^1.7.0",
48 | "babel-register": "^6.26.0",
49 | "chai": "^4.2.0",
50 | "coffeelint": "^2.1.0",
51 | "coffeescript": "^2.3.2",
52 | "coveralls": "^3.0.2",
53 | "ejs": "^2.6.1",
54 | "eslint": "^5.12.1",
55 | "file-loader": "^3.0.1",
56 | "istanbul": "1.1.0-alpha.1",
57 | "mocha": "^5.2.0",
58 | "relative-path-map": "^1.0.1",
59 | "rimraf": "^2.6.3",
60 | "webpack": "^4.29.0"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/cache.js:
--------------------------------------------------------------------------------
1 | const cache = {};
2 |
3 | export function addDependencies(ctx) {
4 | add(ctx, get(ctx));
5 | }
6 |
7 | export function saveDependencies(ctx, deps) {
8 | add(ctx, save(ctx, deps));
9 | }
10 |
11 | function get(ctx) {
12 | return cache[ctx.resourcePath] || [];
13 | }
14 |
15 | function save(ctx, deps=[]) {
16 | cache[ctx.resourcePath] = deps;
17 | return get(ctx);
18 | }
19 |
20 | function add(ctx, deps) {
21 | deps.forEach(name => {
22 | ctx.addDependency(name);
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {getOptions, parseQuery} from 'loader-utils';
2 | import {addDependencies, saveDependencies} from './cache';
3 | import {render} from './render';
4 |
5 | export default function ejsHtmlLoader(src) {
6 | this.cacheable();
7 | let rendered = '';
8 |
9 | try {
10 | rendered = renderTemplate(this, src);
11 | } catch (e) {
12 | emitError(this, e.message);
13 | }
14 |
15 | return rendered;
16 | }
17 |
18 | function renderTemplate(ctx, src) {
19 | let data = getData(ctx);
20 | let {rendered, deps} = render(ctx, src, data);
21 |
22 | saveDependencies(ctx, deps);
23 |
24 | return rendered;
25 | }
26 |
27 | function getData(ctx) {
28 | return Object.assign({}, getOptions(ctx), getResourceQuery(ctx));
29 | }
30 |
31 | function getResourceQuery(ctx) {
32 | return parseQuery(ctx.resourceQuery || "?");
33 | }
34 |
35 | function emitError(ctx, msg) {
36 | addDependencies(ctx);
37 | ctx.emitError(`ejs-html-loader\n${msg}`);
38 | }
39 |
--------------------------------------------------------------------------------
/src/render.js:
--------------------------------------------------------------------------------
1 | import {readFileSync} from 'fs';
2 | import {compile} from 'ejs';
3 |
4 | export function render(ctx, src, data) {
5 | let filename = ctx.resourcePath;
6 | let delimiter = data.delimiter;
7 | let context = data.context;
8 |
9 | let tpl = compile(src, {filename, delimiter, context});
10 |
11 | return {
12 | rendered: tpl(data),
13 | deps: getDeps(tpl.dependencies, {delimiter, context})
14 | };
15 | }
16 |
17 | function getDeps(deps, opts, result=[]) {
18 | deps.forEach(name => {
19 | result.push(name);
20 | getDeps(getOwnDeps(name, opts), opts, result);
21 | });
22 |
23 | return result;
24 | }
25 |
26 | function getOwnDeps(filename, {delimiter, context}) {
27 | let src = readFileSync(filename, 'utf8');
28 | let tpl = compile(src, {filename, delimiter, context});
29 | return tpl.dependencies;
30 | }
31 |
--------------------------------------------------------------------------------
/test/config/paths.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | pathMap = require 'relative-path-map'
3 |
4 | module.exports = pathMap(
5 | root: path.resolve __dirname, '../..'
6 | src: '[root]/src/index.js'
7 | test: '[root]/test'
8 | fixt: '[test]/fixtures'
9 | out: '[test]/.tmp'
10 | outFile: '[out]/index.html'
11 | )
12 |
--------------------------------------------------------------------------------
/test/fixtures/bad-locals.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple
6 |
7 |
8 | <%= headingheadingheading %>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/bad-locals.js:
--------------------------------------------------------------------------------
1 | require('./bad-locals.ejs');
2 |
--------------------------------------------------------------------------------
/test/fixtures/bad-syntax.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 | <%= heading =>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/bad-syntax.js:
--------------------------------------------------------------------------------
1 | require('./bad-syntax.ejs');
2 |
--------------------------------------------------------------------------------
/test/fixtures/context.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple
6 |
7 |
8 | <%= this.heading %>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/context.js:
--------------------------------------------------------------------------------
1 | require('./context.ejs');
2 |
--------------------------------------------------------------------------------
/test/fixtures/delimiter.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple
6 |
7 |
8 | = heading ?>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/delimiter.js:
--------------------------------------------------------------------------------
1 | require('./delimiter.ejs');
2 |
--------------------------------------------------------------------------------
/test/fixtures/include-partial.ejs:
--------------------------------------------------------------------------------
1 | <%= two %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/include.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple
6 |
7 |
8 | <%= one %>
9 | <% include include-partial %>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/fixtures/include.js:
--------------------------------------------------------------------------------
1 | require('./include.ejs');
2 |
--------------------------------------------------------------------------------
/test/fixtures/resource-query.ejs:
--------------------------------------------------------------------------------
1 | <%- one %><%- two %><%- three %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/resource-query.js:
--------------------------------------------------------------------------------
1 | require("./resource-query.ejs?one=abc&two=xyz");
2 |
--------------------------------------------------------------------------------
/test/fixtures/simple.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple
6 |
7 |
8 | <%= heading %>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/simple.js:
--------------------------------------------------------------------------------
1 | require('./simple.ejs');
2 |
--------------------------------------------------------------------------------
/test/helpers/compile.coffee:
--------------------------------------------------------------------------------
1 | {readFile} = require 'fs'
2 | {expect} = require 'chai'
3 | webpack = require 'webpack'
4 | paths = require '../config/paths'
5 | config = require './config'
6 |
7 | module.exports = (match, opts) ->
8 | new Promise((resolve) ->
9 | webpack config(opts), (err) ->
10 | expect(err).not.to.exist
11 |
12 | readFile paths.outFile, 'utf8', (err, content) ->
13 | expect(err).not.to.exist
14 | expect(content).to.match match
15 |
16 | resolve()
17 | )
18 |
--------------------------------------------------------------------------------
/test/helpers/config.coffee:
--------------------------------------------------------------------------------
1 | {resolve} = require 'path'
2 | paths = require '../config/paths'
3 |
4 | module.exports = (opts) ->
5 | context: paths.fixt
6 | entry: resolve paths.fixt, opts.entry
7 | output:
8 | path: paths.out
9 | filename: 'bundle.js'
10 | module:
11 | rules: [
12 | test: /\.ejs$/
13 | use:
14 | loader: 'file-loader?name=index.html'
15 | ,
16 | loader: if opts.query then "#{paths.src}?#{opts.query}" else paths.src,
17 | options: opts.ejsHtml
18 | ]
19 |
--------------------------------------------------------------------------------
/test/helpers/error.coffee:
--------------------------------------------------------------------------------
1 | {expect} = require 'chai'
2 | webpack = require 'webpack'
3 | config = require './config'
4 |
5 | module.exports = (match, opts, done) ->
6 | new Promise((resolve) ->
7 | webpack config(opts), (err, stats) ->
8 | expect(err).not.to.exist
9 | expect(stats.toString()).to.match match
10 |
11 | resolve()
12 | )
13 |
--------------------------------------------------------------------------------
/test/index.coffee:
--------------------------------------------------------------------------------
1 | chai = require 'chai'
2 | rimraf = require 'rimraf'
3 | ejsHtmlLoader = require '../src'
4 | paths = require './config/paths'
5 | compile = require './helpers/compile'
6 | error = require './helpers/error'
7 |
8 | before ->
9 | chai.should()
10 |
11 | describe 'ejs-html-loader', ->
12 |
13 | afterEach (done) ->
14 | rimraf paths.out, done
15 |
16 | it 'exports a function', ->
17 | ejsHtmlLoader.should.be.a 'function'
18 |
19 | context 'options passed as ejsHtml property', ->
20 |
21 | it 'renders template', ->
22 | compile /tuvalu/,
23 | entry: 'simple.js'
24 | ejsHtml:
25 | heading: 'tuvalu'
26 |
27 | context 'options passed as query string', ->
28 |
29 | it 'renders template', ->
30 | compile /togo/,
31 | entry: 'simple.js'
32 | query: 'heading=togo'
33 |
34 | context 'options passed through resource query', ->
35 |
36 | it 'renders template', ->
37 | compile /abcxyz123/,
38 | entry: 'resource-query.js'
39 | ejsHtml:
40 | two: '987'
41 | three: '123'
42 |
43 | context 'with include statement', ->
44 |
45 | it 'includes partial', ->
46 | compile /trinidad[\s\S]*tobago/,
47 | entry: 'include.js'
48 | ejsHtml:
49 | one: 'trinidad'
50 | two: 'tobago'
51 |
52 | context 'option: delimiter', ->
53 |
54 | it 'sets EJS delimiter option', ->
55 | compile /tajikistan/,
56 | entry: 'delimiter.js'
57 | ejsHtml:
58 | heading: 'tajikistan'
59 | delimiter: '?'
60 |
61 | context 'option: context', ->
62 |
63 | it 'sets EJS context option', ->
64 | compile /tibet/,
65 | entry: 'context.js'
66 | ejsHtml:
67 | context:
68 | heading: 'tibet'
69 |
70 | context 'error: undelcared locals', ->
71 |
72 | it 'emits error', ->
73 | error /ejs-html-loader/,
74 | entry: 'bad-locals.js'
75 | ejsHtml:
76 | heading: 'tunisia'
77 |
78 | context 'error: invalaid syntax', ->
79 |
80 | it 'emits error', ->
81 | error /ejs-html-loader/,
82 | entry: 'bad-syntax.js'
83 | ejsHtml:
84 | heading: 'turkey'
85 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --recursive
2 | --reporter spec
3 | --require babel-core/register
4 | --require coffeescript/register
5 |
--------------------------------------------------------------------------------