UNPKG

lml-main

Version:

This is now a mono repository published into many standalone packages.

374 lines (317 loc) 11.9 kB
var loaderUtils = require("loader-utils"); var handlebars = require("handlebars"); var async = require("async"); var util = require("util"); var path = require("path"); var assign = require("object-assign"); var fastreplace = require('./lib/fastreplace'); var findNestedRequires = require('./lib/findNestedRequires'); function versionCheck(hbCompiler, hbRuntime) { return hbCompiler.COMPILER_REVISION === (hbRuntime["default"] || hbRuntime).COMPILER_REVISION; } /** * Check the loader query and webpack config for loader options. If an option is defined in both places, * the loader query takes precedence. * * @param {Loader} loaderContext * @returns {Object} */ function getLoaderConfig(loaderContext) { var query = loaderUtils.getOptions(loaderContext) || {}; var configKey = query.config || 'handlebarsLoader'; var config = loaderContext.options[configKey] || {}; delete query.config; return assign({}, config, query); } module.exports = function(source) { if (this.cacheable) this.cacheable(); var loaderApi = this; var query = getLoaderConfig(loaderApi); var runtimePath = query.runtime || require.resolve("handlebars/runtime"); if (!versionCheck(handlebars, require(runtimePath))) { throw new Error('Handlebars compiler version does not match runtime version'); } var precompileOptions = query.precompileOptions || {}; // Possible extensions for partials var extensions = query.extensions; if (!extensions) { extensions = [".handlebars", ".hbs", ""]; } else if (!Array.isArray(extensions)) { extensions = extensions.split(/[ ,;]/g); } var rootRelative = query.rootRelative; if (rootRelative == null) { rootRelative = "./"; } var foundPartials = {}; var foundHelpers = {}; var foundUnclearStuff = {}; var knownHelpers = {}; [].concat(query.knownHelpers, precompileOptions.knownHelpers).forEach(function(k) { if (k && typeof k === 'string') { knownHelpers[k] = true; } }); var inlineRequires = query.inlineRequires; if (inlineRequires) { inlineRequires = new RegExp(inlineRequires); } var exclude = query.exclude; if (exclude) { exclude = new RegExp(exclude); } var debug = query.debug; var hb = handlebars.create(); var JavaScriptCompiler = hb.JavaScriptCompiler; function MyJavaScriptCompiler() { JavaScriptCompiler.apply(this, arguments); } MyJavaScriptCompiler.prototype = Object.create(JavaScriptCompiler.prototype); MyJavaScriptCompiler.prototype.compiler = MyJavaScriptCompiler; MyJavaScriptCompiler.prototype.nameLookup = function(parent, name, type) { if (debug) { console.log("nameLookup %s %s %s", parent, name, type); } if (type === "partial") { if (name === '@partial-block') { // this is a built in partial, no need to require it return JavaScriptCompiler.prototype.nameLookup.apply(this, arguments); } if (foundPartials["$" + name]) { return "require(" + JSON.stringify(foundPartials["$" + name]) + ")"; } foundPartials["$" + name] = null; return JavaScriptCompiler.prototype.nameLookup.apply(this, arguments); } else if (type === "helper") { if (foundHelpers["$" + name]) { return "__default(require(" + JSON.stringify(foundHelpers["$" + name]) + "))"; } foundHelpers["$" + name] = null; return JavaScriptCompiler.prototype.nameLookup.apply(this, arguments); } else if (type === "context") { // This could be a helper too, save it to check it later if (!foundUnclearStuff["$" + name]) foundUnclearStuff["$" + name] = false; return JavaScriptCompiler.prototype.nameLookup.apply(this, arguments); } else { return JavaScriptCompiler.prototype.nameLookup.apply(this, arguments); } }; if (inlineRequires) { MyJavaScriptCompiler.prototype.pushString = function(value) { if (inlineRequires.test(value)) { this.pushLiteral("require(" + JSON.stringify(value) + ")"); } else { JavaScriptCompiler.prototype.pushString.call(this, value); } }; MyJavaScriptCompiler.prototype.appendToBuffer = function (str) { // This is a template (stringified HTML) chunk if (str.indexOf && str.indexOf('"') === 0) { var replacements = findNestedRequires(str, inlineRequires); str = fastreplace(str, replacements, function (match) { return "\" + require(" + JSON.stringify(match) + ") + \""; }); } return JavaScriptCompiler.prototype.appendToBuffer.apply(this, arguments); }; } hb.JavaScriptCompiler = MyJavaScriptCompiler; // This is an async loader var loaderAsyncCallback = this.async(); var firstCompile = true; var compilationPass = 0; (function compile() { if (debug) { console.log("\nCompilation pass %d", ++compilationPass); } function referenceToRequest(ref, type) { if (/^\$/.test(ref)) { return ref.substring(1); } // Use a relative path for helpers if helper directories are given // unless automatic relative helper resolution has been turned off if (type === 'helper' && query.helperDirs && query.helperDirs.length && rootRelative !== '') { return './' + ref; } return rootRelative + ref; } // Need another compiler pass? var needRecompile = false; // Precompile template var template = ''; try { if (source) { template = hb.precompile(source, assign({ knownHelpersOnly: !firstCompile, // TODO: Remove these in next major release preventIndent: !!query.preventIndent, compat: !!query.compat }, precompileOptions, { knownHelpers: knownHelpers, })); } } catch (err) { return loaderAsyncCallback(err); } var resolve = function(request, type, callback) { var contexts = [loaderApi.context]; // Any additional helper dirs will be added to the searchable contexts if (query.helperDirs) { contexts = contexts.concat(query.helperDirs); } // Any additional partial dirs will be added to the searchable contexts if (query.partialDirs) { contexts = contexts.concat(query.partialDirs); } var resolveWithContexts = function() { var context = contexts.shift(); var traceMsg; if (debug) { traceMsg = path.normalize(path.join(context, request)); console.log("Attempting to resolve %s %s", type, traceMsg); console.log("request=%s", request); } var next = function(err) { if (contexts.length > 0) { resolveWithContexts(); } else { if (debug) console.log("Failed to resolve %s %s", type, traceMsg); return callback(err); } }; loaderApi.resolve(context, request, function(err, result) { if (!err && result) { if (exclude && exclude.test(result)) { if (debug) console.log("Excluding %s %s", type, traceMsg); return next(); } else { if (debug) console.log("Resolved %s %s", type, traceMsg); return callback(err, result); } } else { return next(err); } }); }; resolveWithContexts(); }; var resolveUnclearStuffIterator = function(stuff, unclearStuffCallback) { if (foundUnclearStuff[stuff]) return unclearStuffCallback(); var request = referenceToRequest(stuff.substr(1), 'unclearStuff'); if (query.ignoreHelpers) { unclearStuffCallback(); } else { resolve(request, 'unclearStuff', function(err, result) { if (!err && result) { knownHelpers[stuff.substr(1)] = true; foundHelpers[stuff] = result; needRecompile = true; } foundUnclearStuff[stuff] = true; unclearStuffCallback(); }); } }; var defaultPartialResolver = function defaultPartialResolver(request, callback){ request = referenceToRequest(request, 'partial'); // Try every extension for partials var i = 0; (function tryExtension() { if (i >= extensions.length) { var errorMsg = util.format("Partial '%s' not found", request); return callback(new Error(errorMsg)); } var extension = extensions[i++]; resolve(request + extension, 'partial', function(err, result) { if (!err && result) { return callback(null, result); } tryExtension(); }); }()); }; var resolvePartialsIterator = function(partial, partialCallback) { if (foundPartials[partial]) return partialCallback(); // Strip the # off of the partial name var request = partial.substr(1); var partialResolver = query.partialResolver || defaultPartialResolver; if(query.ignorePartials) { return partialCallback(); } else { partialResolver(request, function(err, resolved){ if(err) { return partialCallback(err); } foundPartials[partial] = resolved; needRecompile = true; return partialCallback(); }); } }; var resolveHelpersIterator = function(helper, helperCallback) { if (foundHelpers[helper]) return helperCallback(); var request = referenceToRequest(helper.substr(1), 'helper'); if (query.ignoreHelpers) { helperCallback(); } else { var defaultHelperResolver = function(request, callback){ return resolve(request, 'helper', callback); }; var helperResolver = query.helperResolver || defaultHelperResolver; helperResolver(request, function(err, result) { if (!err && result) { knownHelpers[helper.substr(1)] = true; foundHelpers[helper] = result; needRecompile = true; return helperCallback(); } // We don't return an error: we just fail to resolve the helper. // This is b/c Handlebars calls nameLookup with type=helper for non-helper // template options, e.g. something that comes from the template data. helperCallback(); }); } }; var doneResolving = function(err) { if (err) return loaderAsyncCallback(err); // Do another compiler pass if not everything was resolved if (needRecompile) { firstCompile = false; return compile(); } // export as module if template is not blank var slug = template ? 'var Handlebars = require(' + JSON.stringify(runtimePath) + ');\n' + 'function __default(obj) { return obj && (obj.__esModule ? obj["default"] : obj); }\n' + 'module.exports = (Handlebars["default"] || Handlebars).template(' + template + ');' : 'module.exports = function(){return "";};'; loaderAsyncCallback(null, slug); }; var resolveItems = function(err, type, items, iterator, callback) { if (err) return callback(err); var itemKeys = Object.keys(items); if (debug) { console.log("Attempting to resolve ", type, ":", itemKeys); } // Resolve path for each item async.each(itemKeys, iterator, callback); }; var resolvePartials = function(err) { resolveItems(err, 'partials', foundPartials, resolvePartialsIterator, doneResolving); }; var resolveUnclearStuff = function(err) { resolveItems(err, 'unclearStuff', foundUnclearStuff, resolveUnclearStuffIterator, resolvePartials); }; var resolveHelpers = function(err) { resolveItems(err, 'helpers', foundHelpers, resolveHelpersIterator, resolveUnclearStuff); }; resolveHelpers(); }()); };