Created
March 18, 2020 18:06
-
-
Save lac5/12da558d6a91da4322a1ea520e4a7e45 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getOptions } from 'loader-utils'; | |
import XRegExp from 'xregexp'; | |
import toSource from 'tosource'; | |
export default function ejsLoader(content) { | |
let options = getOptions(this); | |
let [source, ...imports] = compileEjs(content, options); | |
return ( | |
`${imports.map(code => | |
code + '\n;//\`/***/;' | |
).join('\n')} | |
export default ${source}` | |
); | |
} | |
const reservedWords = [ | |
'abstract', 'arguments','await','boolean','break','byte','case','catch', 'char', | |
'class', 'const','continue','debugger','default','delete','do','double', 'else', | |
'enum', 'eval', 'export', 'extends', 'false','final', 'finally', 'float', 'for', | |
'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', | |
'interface', 'let','long','native','new','null','package','private','protected', | |
'public', 'return', 'short', 'static','super', 'switch', 'synchronized', 'this', | |
'throw', 'throws', 'transient', 'true','try','typeof','var', 'void', 'volatile', | |
'while', 'with', 'yield', | |
]; | |
/** | |
* @param {string} name | |
* @returns {string} | |
*/ | |
export function normalize(name) { | |
if (!name) return ''; | |
let cleanName = String(name) | |
.replace(/^\W+|\W+$/g, '') | |
.replace(/\W+[a-z]/ig, m => | |
m[m.length - 1].toUpperCase()) | |
.replace(/^(?=\d)|\W+/g, '_'); | |
if (reservedWords.index(cleanName) > -1) { | |
cleanName = '_' + cleanName; | |
} | |
if (cleanName !== name) { | |
console.warn('"%s" changed to "%s"', name, cleanName); | |
} | |
return cleanName; | |
} | |
/** | |
* @param {string} content | |
* @param {{ | |
* predefine: { | |
* [name: string]: any | |
* } = {}, | |
* variables: { | |
* locals : string = 'locals', | |
* template : string = 'template', | |
* result : string = 'result', | |
* echo : string = 'echo', | |
* escape : string = 'escape', | |
* } = {}, | |
* entities : RegExp = /[&<>\'"]/g, | |
* escape : RegExp = /<%-([\s\S]*?)(?:%>|$)/, | |
* evaluate : RegExp = /<%(?![-=])([\s\S]*?)(?:%>|$)/, | |
* interpolate: RegExp = /<%=([\s\S]*?)(?:%>|$)/, | |
* extract : RegExp = /<%\^([\s\S]*?)(?:%>|$)/, | |
* matching: ( | |
* 'extract'|'escape'|'interpolate'|'evaluate' | |
* )[] = [ | |
* 'extract','escape','interpolate','evaluate' | |
* ], | |
* async : boolean = false, | |
* minify? : (x: string) => string, | |
* }} [options={}] | |
* @returns {string[]} | |
*/ | |
export function compileEjs(content, { | |
predefine = {}, | |
variables = {}, | |
entities = /[&<>\'"]/g, | |
escape = /<%-([\s\S]*?)(?:%>|$)/, | |
evaluate = /<%(?![-=])([\s\S]*?)(?:%>|$)/, | |
interpolate = /<%=([\s\S]*?)(?:%>|$)/, | |
extract = /<%\^([\s\S]*?)(?:%>|$)/, | |
matching , | |
async , | |
minify , | |
} = {}) { | |
let template = ''; | |
let extracts = []; | |
matching = matching | |
? Array.from(matching).map(normalize).filter(Boolean) | |
: ['extract','escape','interpolate','evaluate']; | |
const rx = XRegExp.build( | |
matching.map(v => `(?<${v}>{{${v}}})|`).join('') + '[\s\S]*?$', | |
{ escape, evaluate, interpolate, extract } | |
); | |
if (typeof minify !== 'function') { | |
minify = x => x; | |
} | |
variables = Object(variables); | |
variables.locals = normalize(variables.locals ) || 'locals' ; | |
variables.template = normalize(variables.template) || 'template'; | |
variables.result = normalize(variables.result ) || 'result' ; | |
variables.echo = normalize(variables.echo ) || 'echo' ; | |
variables.escape = normalize(variables.escape ) || 'escape' ; | |
for (let i = 0, length = content.length; i < length;) { | |
let slice = content.slice(i); | |
let match = XRegExp.exec(slice, rx); | |
if (match[0]) { | |
let code = ''; | |
for (let i = 1, length = match.length; i < length; i++) { | |
if (match[0] === match[i]) { | |
code = match[i + 1] || ''; | |
break; | |
} | |
} | |
let string = minify(slice.slice(0, match.index)); | |
if (string) { | |
template += `${variables.result} += ${JSON.stringify(String(string))};\n`; | |
} | |
if (code) { | |
if (match.extract) { | |
extracts.push(code); | |
} else { | |
template += | |
match.escape ? `${variables.result} += escape(${code})\n;//\`/***/;\n` : | |
match.evaluate ? `${code}\n;//\`/***/;\n` : | |
match.interpolate ? `${variables.result} += (${code}) || ""\n;//\`/***/;\n` : | |
'\n;//\`/***/;\n'; | |
} | |
} | |
i += match.index + match[0].length; | |
} else { | |
template += `${variables.result} += ${JSON.stringify(minify(slice))};\n`; | |
break; | |
} | |
} | |
return [ | |
`${async ? 'async ' : ''}function ${variables.template}(${variables.locals}) { | |
"use strict"; | |
/* predefine *****************/ | |
${Object.entries(predefine).map(([name, value]) => { | |
name = normalize(name); | |
return (name ? | |
` var ${normalize(name)} = ${toSource(value)};` : | |
``); | |
}).join('\n')} | |
/* /predefine ****************/ | |
var ${variables.result} = ""; | |
var ${variables.echo} = function(input) { | |
${variables.result} += input; | |
}; | |
var ${variables.escape} = function(input) { | |
return ("" + (input || "")).replace(${toSource(entities)}, function (m) { | |
return m.replace(/[\s\S]/g, function (c) { | |
return "&#" + c.charCodeAt(0) + ";"; | |
}); | |
}); | |
}; | |
/* template ******************/ | |
${template} | |
/* /template *****************/ | |
return result; | |
}` , | |
...extracts, | |
]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment