UNPKG

6.07 kBJavaScriptView Raw
1/**
2 * 此插件为 yog-view 的 swig 版本显现。
3 * 依赖 yog-view。
4 */
5var Readable = require('stream').Readable;
6var util = require('util');
7var Swig = require('swig').Swig;
8var loader = require('./lib/loader.js');
9var debuglog = require('debuglog')('yog-swig');
10var LRU = require('lru-cache');
11var tags = [
12 'script',
13 'style',
14 'html',
15 'body',
16 'require',
17 'uri',
18 'widget',
19 'head',
20 'feature',
21 'featureelse',
22 'spage'
23];
24
25var swigInstance;
26
27var EngineStream = function(swig, view, locals) {
28 this.swig = swig;
29 this.view = view;
30 this.locals = locals;
31 this.reading = false;
32 Readable.call(this);
33};
34
35util.inherits(EngineStream, Readable);
36
37EngineStream.prototype._read = function() {
38 var self = this;
39 // var state = self._readableState;
40 if (this.reading) {
41 return;
42 }
43 this.reading = true;
44 debuglog('start render [%s]', this.view);
45 this.swig.renderFile(this.view, this.locals, function(error, output) {
46 if (error) {
47 debuglog('render [%s] failed', self.view);
48 return self.emit('error', error);
49 }
50 debuglog('render [%s] succ', self.view);
51 self.push(output);
52 self.push(null);
53 });
54};
55
56/**
57 * Opitions 说明
58 * - `views` 模板根目录
59 * - `loader` 模板加载器,默认自带,可选。
60 *
61 * 更多细节请查看 yog-view
62 *
63 */
64var SwigWrap = module.exports = function SwigWrap(app, options) {
65 options.renderCacheOptions = options.renderCacheOptions || {};
66 var max = options.renderCacheOptions.max || 20 * 1024 * 1024;
67 var pruneRate = options.renderCacheOptions.pruneRate || 1;
68 var renderCaches = LRU({
69 max: max,
70 length: function(n, key) {
71 return n.length;
72 },
73 maxAge: options.renderCacheOptions.maxAge || 1000 * 60 * 60
74 });
75 renderCaches.setCount = 0;
76 if (swigInstance) {
77 debuglog('use swig instance cache');
78 this.swig = swigInstance;
79 return;
80 }
81
82 debuglog('init swig instance');
83
84 options = options || {};
85
86 // 重写 loader, 让模板引擎,可以识别静态资源标示。如:example:static/lib/jquery.js
87 options.loader = options.loader || loader(app, options.views, options.encoding);
88
89 var swig = this.swig = swigInstance = new Swig(options);
90
91 // 加载内置扩展
92 tags.forEach(function(tag) {
93 var t = require('./tags/' + tag);
94 swig.setTag(tag, t.parse, t.compile, t.ends, t.blockLevel || false);
95 });
96
97 // 加载用户扩展
98 options.tags && Object.keys(options.tags).forEach(function(name) {
99 var t = options.tags[name];
100 swig.setTag(name, t.parse, t.compile, t.ends, t.blockLevel || false);
101 });
102
103 options.filters && Object.keys(options.filters).forEach(function(name) {
104 var t = options.filters[name];
105 swig.setFilter(name, t);
106 });
107
108 swig.renderCache = options.renderCache || {
109 get: renderCaches.get.bind(renderCaches),
110 set: function (key, value) {
111 // 设置过多缓存时,考虑清理老缓存请求次数
112 if (renderCaches.setCount > pruneRate * max) {
113 renderCaches.prune();
114 renderCaches.setCount = 0;
115 debuglog('prune widget cache');
116 }
117 renderCaches.setCount++;
118 return renderCaches.set(key, value);
119 },
120 clean: function() {
121 renderCaches.reset();
122 }
123 }
124};
125
126SwigWrap.prototype.cleanCache = function() {
127 try {
128 this.swig.invalidateCache();
129 this.swig.renderCache.clean && this.swig.renderCache.clean();
130 } catch (e) {}
131};
132
133SwigWrap.prototype.makeStream = function(view, locals) {
134 debuglog('create [%s] render stream', view);
135 return new EngineStream(this.swig, view, locals);
136};
137
138
139// 扩展swig内置函数,用于提供bigpipe支持
140Swig.prototype._w = Swig.prototype._widget = function(layer, id, attr, options) {
141 var self = this;
142 var pathname = layer.resolve(id);
143 var cacheKey = attr.cache ? id + '_' + attr.cache : null;
144
145 if (!layer.supportBigPipe() || !attr.mode || attr.mode === 'sync') {
146 layer.load(id);
147 if (cacheKey) {
148 var cacheContent = self.renderCache.get(cacheKey);
149 if (cacheContent) {
150 debuglog('load render cache by [%s]', cacheKey);
151 return function (locals) {
152 return cacheContent;
153 };
154 }
155 }
156 var res = this.compileFile(pathname, options);
157 var newRes = function(locals) {
158 var contents = res(locals);
159 if (cacheKey) {
160 debuglog('set render cache [%s]', cacheKey);
161 self.renderCache.set(cacheKey, contents);
162 }
163 return contents;
164 }
165 newRes.tokens = res.tokens;
166 newRes.parent = res.parent;
167 newRes.blocks = res.blocks;
168 return newRes;
169 }
170
171 return function(locals) {
172 var container = attr['container'] || attr['for'];
173 var pageletOptions = {
174 container: container,
175 model: attr.model,
176 id: attr.id,
177 lazy: attr.lazy === 'true',
178 mode: attr.mode,
179 locals: locals,
180 view: pathname,
181 viewId: id,
182 compiled: function(locals) {
183 var fn = self.compileFile(pathname, options);
184 locals._yog.load(id);
185 return fn.apply(this, arguments);
186 }
187 };
188
189 if (layer.bigpipe.isSpiderMode) {
190 var syncPagelet = new layer.bigpipe.Pagelet(pageletOptions);
191 syncPagelet.start(layer.bigpipe.pageletData[attr.id], true);
192 return container ? syncPagelet.html : '<div id="' + attr.id + '"> ' + syncPagelet.html + '</div>';
193 } else {
194 container = attr['container'] || attr['for'];
195 layer.addPagelet(pageletOptions);
196 return container ? '' : '<div id="' + attr.id + '"></div>';
197 }
198 };
199};