forked from at0g/nunjucks-loader
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
199 lines (163 loc) · 7.83 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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*******************************************************************
*
* This module was heavily inspired by nunjucksify.
* (https://www.npmjs.com/package/nunjucksify)
*
* Full credit to the original authors.
*
******************************************************************/
var nunjucks = require('nunjucks');
var slash = require('slash');
var path = require('path');
var fs = require('fs');
var loaderUtils = require('loader-utils');
var env;
var hasRun = false;
var pathToConfigure;
var jinjaCompatStr;
var root;
module.exports = function (source) {
if (this.target !== 'web') {
throw new Error('[nunjucks-loader] non-web targets are not supported');
}
this.cacheable();
if (!hasRun){
var query = loaderUtils.parseQuery(this.query);
var envOpts = query.opts || {};
if (query){
env = new nunjucks.Environment([], envOpts);
if (query.config){
pathToConfigure = query.config;
try {
var configure = require(query.config);
configure(env);
}
catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
if (!query.quiet) {
var message = 'Cannot configure nunjucks environment before precompile\n' +
'\t' + e.message + '\n' +
'Async filters and custom extensions are unsupported when the nunjucks\n' +
'environment configuration depends on webpack loaders or custom module\n' +
'resolve rules. If you are not using async filters or custom extensions\n' +
'with nunjucks, you can safely ignore this warning.'
;
this.emitWarning(message);
}
}
else {
this.emitError(e.message);
}
}
}
// Specify the template search path, so we know from what directory
// it should be relative to.
if (query.root) {
root = query.root;
}
// Enable experimental Jinja compatibility to be enabled
if(query.jinjaCompat){
jinjaCompatStr = 'nunjucks.installJinjaCompat();\n';
}
}
else {
env = new nunjucks.Environment([]);
}
hasRun = true;
}
// ==============================================================================
// inject all named imports that are marked with "{#browserinject#}" suffix
// ==============================================================================
let injectedTemplates = [];
const injectTemplate = function (source, match, filepath, name, pathContext){
source = source.replace(new RegExp(match, 'g'), '');
let templatePath = path.resolve(pathContext, filepath);
let id = path.basename(templatePath + name);
if(injectedTemplates.indexOf(id) === -1){
injectedTemplates.push(id);
let data = fs.readFileSync(templatePath).toString();
let macroDefReg = /{%\smacro\srender\(/g;
data = data.replace(macroDefReg, '{% macro '+name+'_render(');
data = replaceImports(data, path.dirname(templatePath), false);
source = data + source;
}
let macroUseReg = new RegExp(name+'.render\\(', 'g');
source = source.replace(macroUseReg, name+'_render(');
return source;
}
const replaceImports = function(source, pathContext, initial){
let importRegPattern = '{%\\simport\\s(["\'])([A-Z-a-z0-9/.-_]+)(["\'])\\sas\\s(\\S+)\\s%}';
if(initial){
importRegPattern += '{#browserinject#}';
}
let importReg = new RegExp(importRegPattern, 'g');
let newSource = ''+source;
while (match = importReg.exec(source)) {
newSource = injectTemplate(newSource, match[0], match[2], match[4], pathContext)
}
return newSource;
}
source = replaceImports(source, this.context, true);
// ==============================================================================
// precompile the source string
// ==============================================================================
var name = slash(path.relative(root || this.rootContext || this.options.context, this.resourcePath));
var nunjucksCompiledStr = nunjucks.precompileString(source, {
env: env,
name: name
});
nunjucksCompiledStr = nunjucksCompiledStr.replace(/window\.nunjucksPrecompiled/g, 'nunjucks.nunjucksPrecompiled');
// ==============================================================================
// replace 'require' filter with a webpack require expression (to resolve assets)
// ==============================================================================
var filterReg = /env\.getFilter\(\"require\"\)\.call\(context, \"(.*?)\"/g;
nunjucksCompiledStr = nunjucksCompiledStr.replace(filterReg, 'require("$1"');
// ================================================================
// Begin to write the compiled template output to return to webpack
// ================================================================
var compiledTemplate = '';
compiledTemplate += 'var nunjucks = require("nunjucks/browser/nunjucks-slim");\n';
if (jinjaCompatStr) {
compiledTemplate += jinjaCompatStr + '\n';
}
compiledTemplate += 'var env;\n';
compiledTemplate += 'if (!nunjucks.currentEnv){\n';
compiledTemplate += '\tenv = nunjucks.currentEnv = new nunjucks.Environment([], ' + JSON.stringify(envOpts) + ');\n';
compiledTemplate += '} else {\n';
compiledTemplate += '\tenv = nunjucks.currentEnv;\n';
compiledTemplate += '}\n';
if (pathToConfigure) {
compiledTemplate += 'var configure = require("' + slash(path.relative(this.context, pathToConfigure)) + '")(env);\n';
}
// =========================================================================
// Find template dependencies within nunjucks (extends, import, include etc)
// =========================================================================
//
// Create an object on nunjucks to hold the template dependencies so that they persist
// when this loader compiles multiple templates.
compiledTemplate += 'var dependencies = nunjucks.webpackDependencies || (nunjucks.webpackDependencies = {});\n';
var templateReg = /env\.getTemplate\(\"(.*?)\"/g;
var match;
// Create an object to store references to the dependencies that have been included - this ensures that a template
// dependency is only written once per file, even if it is used multiple times.
var required = {};
// Iterate over the template dependencies
while (match = templateReg.exec(nunjucksCompiledStr)) {
var templateRef = match[1];
if (!required[templateRef]) {
// Require the dependency by name, so it gets bundled by webpack
compiledTemplate += 'dependencies["' + templateRef + '"] = require( "' + templateRef + '" );\n';
required[templateRef] = true;
}
}
compiledTemplate += '\n\n\n\n';
// Include a shim module (by reference rather than inline) that modifies the nunjucks runtime to work with the loader.
compiledTemplate += 'var shim = require("' + slash(path.resolve(this.context, __dirname + '/runtime-shim')) + '");\n';
compiledTemplate += '\n\n';
// Write the compiled template string
compiledTemplate += nunjucksCompiledStr + '\n';
compiledTemplate += '\n\n';
// export the shimmed module
compiledTemplate += 'module.exports = shim(nunjucks, env, nunjucks.nunjucksPrecompiled["' + name + '"] , dependencies)';
return compiledTemplate;
};