/
index.js
88 lines (83 loc) · 3.33 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// # Micro Template Loader
//
// This is a [Webpack][] [Loader][]. It converts **HTML** files to a
// Javascript function which can be passed a data model. You can use
// [EJS][]-style tags to break out in to vanilla Javascript to do anything you
// need. However, best practice dictates that you should keep logic to a
// minimum in your templates.
//
// [Webpack]: http://webpack.github.io/
// [Loader]: http://webpack.github.io/docs/using-loaders.html
// [EJS]: http://embeddedjs.com/
"use strict";
var crypto = require("crypto")
var openTag = "<%"
var closeTag = "%>"
var rWhitespace = /[\r\n\s]+/g
var buildDeps = require("./helpers")
// Webpack Loader Definition
// ---
//
// Receive HTML input from Webpack and return CommonJS-formatted Javascript.
module.exports = function microTemplateLoader(content) {
this.cacheable && this.cacheable()
var i, segment, first, len, concat
var usedModules = []
var min = !!this.minimize
var outputStr = ""
var continuable = false
// Create a unique output variable name to discourage tampering from
// within an unescaped sequence. The name doesn't really matter and will
// matter much if it's getting passed to UglifyJS.
var hash = crypto.createHash("md5").update(content)
var outName = "_" + hash.digest("hex").substring(0, 8)
// Replace identifiers in `content` with a special character so the parser
// knows how to differentiate between HTML & JS.
content = content.split(openTag).join(closeTag + "\x11").split(closeTag)
// Begin parsing `content` which is now an array of strings. Some of the
// strings start with our special character `\x11` which means that string
// should be treated as EJS. We start out with `_o = ""`.
len = content.length
concat = (min ? "" : "\n\t") + outName + " = "
for (i = 0; i < len; i++ ) {
segment = content[i]
if (!segment) continue;
if (segment.charAt(0) === "\x11") {
first = segment.charAt(1)
// Handle EJS Escaped/Unescaped values.
if (first === "=" || first === "!") {
outputStr += (continuable ? " + " : concat) +
(first == "=" ? "escape(" : "(") + segment.substring(2).trim() + ")"
continuable = true
if (first === "=") usedModules.push("escape")
// If this was the first segment, start an empty string first just
// in case `segment` is the start of a `for` loop. Close the
// current string `continuable` if it's open and break out in to
// Vanilla JS.
} else {
if (!outputStr) outputStr = concat + "\"\""
if (continuable) outputStr += ";"
outputStr += (min ? "\n" : "\n\t") + segment.substring(1).trim()
continuable = false
}
// If `segment` is plain HTML, start a `continuable`. Otherwise, just
// append it to `outputStr`.
} else {
if (min) { segment = segment.replace(rWhitespace, " ") }
outputStr += (continuable ? (min ? " + " : " +\n\t\t") : concat) +
JSON.stringify(segment)
continuable = true
}
// Switch to `_o += ` for string concatenation and move on to the next
// `segment`.
concat = (min ? "" : "\n\t") + outName + " += "
}
// Build dependencies (see [helpers](./helpers.html)) and inject the
// generated code in to a function.
usedModules = usedModules.reduce(buildDeps, "")
return (usedModules ? "var " + usedModules + "\n\n" : "") +
"module.exports = function microTemplate(data) {\n" +
"\tvar " + outputStr.trim() + "\n" +
"\treturn " + outName + ";\n" +
"}";
}