`

ejs源码

 
阅读更多

与artTemplate大不同的是

artTemplate使用split分割模板字符串;ejs通过基于正则的match方法分割模板字符串。

artTemplate通过传入辅助函数的引用拼接编译函数字符串;ejs有赖于js原生语句实现each、if语法,普通内容直接push到__output中,构建编译函数字符串。

 

1.ejs.js

/*
 * EJS Embedded JavaScript templates
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

/**
 * @file Embedded JavaScript templating engine.
 * @author Matthew Eernisse <mde@fleegix.org>
 * @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
 * @project EJS
 * @license {@link http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0}
 */

/**
 * EJS internal functions.
 *
 * Technically this "module" lies in the same file as {@link module:ejs}, for
 * the sake of organization all the private functions re grouped into this
 * module.
 *
 * @module ejs-internal
 * @private
 */

/**
 * Embedded JavaScript templating engine.
 *
 * @module ejs
 * @public
 */

var fs = require('fs');
var path = require('path');
var utils = require('./utils');

var scopeOptionWarned = false;
var _VERSION_STRING = require('../package.json').version;
var _DEFAULT_DELIMITER = '%';
var _DEFAULT_LOCALS_NAME = 'locals';
var _NAME = 'ejs';
var _REGEX_STRING = '(<%%|%%>|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)';// 开启闭合标签的默认匹配正则
var _OPTS = ['delimiter', 'scope', 'context', 'debug', 'compileDebug',
  'client', '_with', 'rmWhitespace', 'strict', 'filename'];
var _BOM = /^\uFEFF/;

// 缓存编译函数
exports.cache = utils.cache;

exports.localsName = _DEFAULT_LOCALS_NAME;

// 获取子模板的绝对路径
// 当前模板设置filename文件或目录路径,name传参为相对filename目录的文件路径
exports.resolveInclude = function(name, filename, isDir) {
    var dirname = path.dirname;
    var extname = path.extname;
    var resolve = path.resolve;
    var includePath = resolve(isDir ? filename : dirname(filename), name);
    var ext = extname(name);
    if (!ext) {
        includePath += '.ejs';
    }
    return includePath;
};

// 获取子模板的绝对路径
function getIncludePath(path, options){
    var includePath;
    if (path.charAt(0) == '/') {
        includePath = exports.resolveInclude(path.replace(/^\/*/,''), options.root || '/', true);
    } else {
      if (!options.filename) {
          throw new Error('`include` use relative path requires the \'filename\' option.');
      }
      includePath = exports.resolveInclude(path, options.filename);
    }
    return includePath;
}

// 缓存中有编译函数,获取编译函数;若无,通过文件名读取文件后,获得编译函数,并存入缓存
function handleCache(options, template) {
    var func;
    var filename = options.filename;
    var hasTemplate = arguments.length > 1;

    if (options.cache) {
        if (!filename) {
            throw new Error('cache option requires a filename');
        }
        func = exports.cache.get(filename);
        if (func) {
            return func;
        }
        if (!hasTemplate) {
            template = fs.readFileSync(filename).toString().replace(_BOM, '');
        }
    } else if (!hasTemplate) {
        if (!filename) {
          throw new Error('Internal EJS error: no file name or template '
                        + 'provided');
        }
        template = fs.readFileSync(filename).toString().replace(_BOM, '');
    }
    func = exports.compile(template, options);
    if (options.cache) {
        exports.cache.set(filename, func);
    }
    return func;
}

// 通过文件路径获取编译函数
function includeFile(path, options) {
    var opts = utils.shallowCopy({}, options);
    opts.filename = getIncludePath(path, opts);
    // handleCache 缓存中有编译函数,获取编译函数;若无,通过文件名读取文件后,获得编译函数,并存入缓存
    return handleCache(opts);
}

// 获取嵌套模板的编译函数核心字符串、模板字符串内容
function includeSource(path, options) {
    var opts = utils.shallowCopy({}, options);
    var includePath;
    var template;
    includePath = getIncludePath(path,opts);
    template = fs.readFileSync(includePath).toString().replace(_BOM, '');
    opts.filename = includePath;
    var templ = new Template(template, opts);
    templ.generateSource();
    return {
        source: templ.source,
        filename: includePath,
        template: template
    };
}

/**
 * 报错
 *
 * @param {Error}  err      Error object
 * @param {String} str      编译核心核心内容,字符串形式
 * @param {String} filename 文件名
 * @param {String} lineno   错误的行号
 */
function rethrow(err, str, flnm, lineno){
    var lines = str.split('\n');
    var start = Math.max(lineno - 3, 0);
    var end = Math.min(lines.length, lineno + 3);
    var filename = utils.escapeXML(flnm);

    var context = lines.slice(start, end).map(function (line, i){
        var curr = i + start + 1;
        return (curr == lineno ? ' >> ' : '    ')
          + curr
          + '| '
          + line;
    }).join('\n');

    err.path = filename;
    err.message = (filename || 'ejs') + ':'
        + lineno + '\n'
        + context + '\n\n'
        + err.message;

    throw err;
}

// 去除";"及其空格
function stripSemi(str) {
    return str.replace(/;(\s*$)/, '$1');
}

// 编译模板字符串template,返回编译函数,编译配置为opts
exports.compile = function compile(template, opts) {
    var templ;

    // 旧版本的scope替换为context
    if (opts && opts.scope) {
        if (!scopeOptionWarned){
            console.warn('`scope` option is deprecated and will be removed in EJS 3');
            scopeOptionWarned = true;
        }
        if (!opts.context) {
            opts.context = opts.scope;
        }
        delete opts.scope;
    }
    templ = new Template(template, opts);
    return templ.compile();
};

// 通过模板路径和模板数据、编译配置获取编译结果
exports.render = function (template, d, o) {
    var data = d || {};
    var opts = o || {};

    // No options object -- if there are optiony names
    // in the data, copy them to options
    if (arguments.length == 2) {
      utils.shallowCopyFromList(opts, data, _OPTS);
    }

    // handleCache 缓存中有编译函数,获取编译函数;若无,通过文件名读取文件后,获得编译函数,并存入缓存
    return handleCache(opts, template)(data);
};

/**
 * 通过模板路径和模板数据获取编译结果,并执行回调
 * @param {String}            filename  模板路径
 * @param {Object}            [data={}] 模板数据
 * @param {Options}           [opts={}] 编译配置
 * @param {RenderFileCallback} cb 回调函数
 */
exports.renderFile = function () {
    var args = Array.prototype.slice.call(arguments);
    var filename = args.shift();
    var cb = args.pop();
    var data = args.shift() || {};
    var opts = args.pop() || {};
    var optsKeys =_OPTS.slice();
    var result;

    opts = utils.shallowCopy({}, opts);

    // `renderFile`方法添加"cache"为可配置内容
    optsKeys.push('cache');

    if (arguments.length == 3) {
        // Express 4
        if (data.settings && data.settings['view options']) {
            utils.shallowCopyFromList(opts, data.settings['view options'], optsKeys);
        }
        // Express 3 and lower
        else {
            utils.shallowCopyFromList(opts, data, optsKeys);
        }
    }
    opts.filename = filename;

    try {
        // handleCache 缓存中有编译函数,获取编译函数;若无,通过文件名读取文件后,获得编译函数,并存入缓存
        result = handleCache(opts)(data);
    }catch(err) {
        return cb(err);
    }
    return cb(null, result);
};

// 清空模板编译函数
exports.clearCache = function () {
    exports.cache.reset();
};

function Template(text, opts) {
    opts = opts || {};
    var options = {};
    this.templateText = text;// 模板字符串
    this.mode = null;// 模板字符串各line处理模式
    this.truncate = false;
    this.currentLine = 1;// 错误提示时输出行号
    this.source = '';// 编译函数体核心内容,字符串形式
    this.dependencies = [];// 记录当前模板包含的模板
    options.client = opts.client || false;// 客户端或服务端不同模块将escape、rethow函数注入到编译函数体内
    options.escapeFunction = opts.escape || utils.escapeXML;// html转义
    options.compileDebug = opts.compileDebug !== false;// 是否开启调试模板编译模板
    options.debug = !!opts.debug;// 是否以字符串形式输出编译函数
    options.filename = opts.filename;// 模板名,作为模板的标识符,缓存编译函数的键值
    options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;// 开启或者闭合尖括号所用的字符
    options.strict = opts.strict || false;// 编译函数采用严格模式 "use strict",严格模式下不能使用with语句
    options.context = opts.context;// 编译函数上下文
    options.cache = opts.cache || false;// 是否开启编译函数缓存
    options.rmWhitespace = opts.rmWhitespace;// 是否清除起始的空格
    options.root = opts.root;
    options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;

    if (options.strict) { // var obj={a:1};with(obj){console.log(a)} with语句快速引用对象的属性与方法
        options._with = false;
    }else {
        options._with = typeof opts._with != 'undefined' ? opts._with : true;
    }

    this.opts = options;

    this.regex = this.createRegex();// 开启闭合标签的匹配正则
}

Template.modes = {
    EVAL: 'eval',// "<%  %>"、"<%_  %>"内语句原样拼接到编译函数体内,可承接js语句,后跟模板字符串通过push方法添加到__output
    ESCAPED: 'escaped',// "<%=  %>"内语句通过html转义后,可承接变量,由push方法添加到__output
    RAW: 'raw',// "<%-  %>"不转义,可承接变量,由push方法添加到__output
    COMMENT: 'comment',// "<%#  %>"注释,不添加编译函数体内
    LITERAL: 'literal'// "<%%  %%>"通过push方法将"<%  %>"由push方法添加到__output
};

Template.prototype = {
    // 根据delimiter配置,重新生成开启闭合标签的匹配正则
    createRegex: function () {
        var str = _REGEX_STRING;
        var delim = utils.escapeRegExpChars(this.opts.delimiter);
        str = str.replace(/%/g, delim);
        return new RegExp(str);
    },

    // 获取编译函数,客户端或者服务器端不同模块间将escape、include函数以字符串形式写入
    // 同artTemplate不同的是,artTemplate主要面向客户端,$utils、$helpers都通过引用的方式构建传入
    //    ejs面向服务端,each语法借用js的原生语句,escape、include函数以参数或字符串形式传入
    //    两者都同样以拼接字符串的方式生成编译函数体,再通过new Function构建函数
    compile: function () {
        var src;
        var fn;
        var opts = this.opts;
        var prepended = '';
        var appended = '';
        var escape = opts.escapeFunction;

        if (!this.source) {
            // 将模板字符串通过match方法拆分为开闭标签前后各段,按条件拼接编译函数体,或者作为js语句,或者作为普通字符串
            this.generateSource();
            prepended += '  var __output = [], __append = __output.push.bind(__output);' + '\n';
            if (opts._with !== false) {
                prepended +=  '  with (' + opts.localsName + ' || {}) {' + '\n';
                appended += '  }' + '\n';
            }
            appended += '  return __output.join("");' + '\n';
            this.source = prepended + this.source + appended;
        }

        if (opts.compileDebug) {
            src = 'var __line = 1' + '\n'
                + '  , __lines = ' + JSON.stringify(this.templateText) + '\n'
                + '  , __filename = ' + (opts.filename ?
                      JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
                + 'try {' + '\n'
                + this.source
                + '} catch (e) {' + '\n'
                + '  rethrow(e, __lines, __filename, __line);' + '\n'
                + '}' + '\n';
        }else {
            src = this.source;
        }

        if (opts.debug) {
            console.log(src);
        }

        // 客户端将escape、rethow函数注入到编译函数体内
        if (opts.client) {
            src = 'escape = escape || ' + escape.toString() + ';' + '\n' + src;
            if (opts.compileDebug) {
                src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
            }
        }

        if (opts.strict) {
            src = '"use strict";\n' + src;
        }

        try {
            fn = new Function(opts.localsName + ', escape, include, rethrow', src);
        }catch(e) {
            if (e instanceof SyntaxError) {
                if (opts.filename) {
                    e.message += ' in ' + opts.filename;
                }
                e.message += ' while compiling ejs\n\n';
                e.message += 'If the above error is not helpful, you may want to try EJS-Lint:\n';
                e.message += 'https://github.com/RyanZim/EJS-Lint';
            }
            throw e;
        }

        if (opts.client) {
            fn.dependencies = this.dependencies;
            return fn;
        }

        var returnedFn = function (data) {
            var include = function (path, includeData) {
                var d = utils.shallowCopy({}, data);
                if (includeData) {
                    d = utils.shallowCopy(d, includeData);
                }
                // includeFile 通过文件路径获取编译函数
                return includeFile(path, opts)(d);
            };
            return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
        };
        returnedFn.dependencies = this.dependencies;
        return returnedFn;
    },

    // 将模板字符串通过match方法拆分为开闭标签前后各段,按条件拼接编译函数体,或者作为js语句,或者作为普通字符串
    generateSource: function () {
        var opts = this.opts;

        if (opts.rmWhitespace) {
            this.templateText = this.templateText.replace(/\r/g, '').replace(/^\s+|\s+$/gm, '');
        }

        // 清除<%_前、_%>后的制表符\t及空格
        this.templateText = this.templateText.replace(/[ \t]*<%_/gm, '<%_').replace(/_%>[ \t]*/gm, '_%>');

        var self = this;

        // 模板字符串清除开闭标签,以数组形式记录开闭标签及其前后字符串,并返回
        var matches = this.parseTemplateText();
        var d = this.opts.delimiter;

        if (matches && matches.length) {
            matches.forEach(function (line, index) {
                var opening;
                var closing;
                var include;
                var includeOpts;
                var includeObj;
                var includeSrc;
                
                // 开始标签存在时,校验闭合标签是否存在
                if ( line.indexOf('<' + d) === 0 && line.indexOf('<' + d + d) !== 0 ) {
                    closing = matches[index + 2];// 闭合标签
                    if (!(closing == d + '>' || closing == '-' + d + '>' || closing == '_' + d + '>')) {
                        throw new Error('Could not find matching close tag for "' + line + '".');
                    }
                }

                // 拼接include子模板字符串
                if ((include = line.match(/^\s*include\s+(\S+)/))) {
                    opening = matches[index - 1];
                    if (opening && (opening == '<' + d || opening == '<' + d + '-' || opening == '<' + d + '_')) {
                        // utils.shallowCopy 对象浅拷贝
                        includeOpts = utils.shallowCopy({}, self.opts);
                        // 获取嵌套模板的编译函数核心字符串、模板字符串内容
                        includeObj = includeSource(include[1], includeOpts);
                        if (self.opts.compileDebug) {
                            includeSrc =
                                '    ; (function(){' + '\n'
                                + '      var __line = 1' + '\n'
                                + '      , __lines = ' + JSON.stringify(includeObj.template) + '\n'
                                + '      , __filename = ' + JSON.stringify(includeObj.filename) + ';' + '\n'
                                + '      try {' + '\n'
                                + includeObj.source
                                + '      } catch (e) {' + '\n'
                                + '        rethrow(e, __lines, __filename, __line);' + '\n'
                                + '      }' + '\n'
                                + '    ; }).call(this)' + '\n';
                        }else{
                            includeSrc = '    ; (function(){' + '\n' + includeObj.source +
                                '    ; }).call(this)' + '\n';
                        }
                        self.source += includeSrc;
                        self.dependencies.push(exports.resolveInclude(include[1],
                            includeOpts.filename));
                        return;
                    }
                }

                // line为开闭标签,及其内外的各项字符串,根据开闭标签拼接编译函数字符串
                self.scanLine(line);
            });
        }
    },

    // 模板字符串清除开闭标签,以数组形式记录开闭标签及其前后字符串,并返回
    parseTemplateText: function () {
        var str = this.templateText;
        var pat = this.regex;// 开启闭合标签的匹配正则
        var result = pat.exec(str);// 获取str所有开启闭合标签,index属性为其在str中的序号
        var arr = [];
        var firstPos;

        while (result) {
            firstPos = result.index;

            if (firstPos !== 0) {
              arr.push(str.substring(0, firstPos));
              str = str.slice(firstPos);
            }

            arr.push(result[0]);
            str = str.slice(result[0].length);
            result = pat.exec(str);
        }

        if (str) {
            arr.push(str);
        }

        return arr;
    },

    // line为开闭标签,及其内外的各项字符串,根据开闭标签拼接编译函数字符串
    scanLine: function (line) {
        var self = this;
        var d = this.opts.delimiter;// 开启或者闭合尖括号所用的字符
        var newLineCount = 0;

        // 普通字符串处理
        function _addOutput() {
            if (self.truncate) {
                line = line.replace(/^(?:\r\n|\r|\n)/, '');
                self.truncate = false;
            } else if (self.opts.rmWhitespace) {
                line = line.replace(/^\n/, '');
            }

            if (!line) {
                return;
            }

            // 转义
            line = line.replace(/\\/g, '\\\\');
            line = line.replace(/\n/g, '\\n');
            line = line.replace(/\r/g, '\\r');
            line = line.replace(/"/g, '\\"');

            self.source += '    ; __append("' + line + '")' + '\n';
        }

        newLineCount = (line.split('\n').length - 1);

        switch (line) {
            // "<%  %>"、"<%_  %>"内语句原样拼接到编译函数体内,后跟模板字符串通过push方法添加到__output
            case '<' + d:
            case '<' + d + '_':
                this.mode = Template.modes.EVAL;
                break;
            // "<%=  %>"内语句通过html转义后,可承接变量,由push方法添加到__output
            case '<' + d + '=':
                this.mode = Template.modes.ESCAPED;
                break;
            // "<%-  %>"不转义,可承接变量
            case '<' + d + '-':
                this.mode = Template.modes.RAW;
                break;
            // "<%#  %>"评论,不添加编译函数体内
            case '<' + d + '#':
                this.mode = Template.modes.COMMENT;
                break;
            // "<%%  %%>"通过push方法将"<%  %>"由push方法添加到__output
            case '<' + d + d:
                this.mode = Template.modes.LITERAL;
                this.source += '    ; __append("' + line.replace('<' + d + d, '<' + d) + '")' + '\n';
                break;
            case d + d + '>':
                this.mode = Template.modes.LITERAL;
                this.source += '    ; __append("' + line.replace(d + d + '>', d + '>') + '")' + '\n';
                break;
            case d + '>':
            case '-' + d + '>':
            case '_' + d + '>':
                if (this.mode == Template.modes.LITERAL) {
                    _addOutput();
                }

                this.mode = null;
                this.truncate = line.indexOf('-') === 0 || line.indexOf('_') === 0;
                break;
            default:
                // 开闭标签包裹的内容
                if (this.mode) {
                    // 单行注释后没有"\n",添加"\n"
                    switch (this.mode) {
                        case Template.modes.EVAL:
                        case Template.modes.ESCAPED:
                        case Template.modes.RAW:
                            if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
                                line += '\n';
                            }
                    }

                    switch (this.mode) {
                        case Template.modes.EVAL:
                            this.source += '    ; ' + line + '\n';
                            break;
                        case Template.modes.ESCAPED:
                            this.source += '    ; __append(escape(' + stripSemi(line) + '))' + '\n';
                            break;
                        case Template.modes.RAW:
                            this.source += '    ; __append(' + stripSemi(line) + ')' + '\n';
                            break;
                        case Template.modes.COMMENT:
                            break;
                        case Template.modes.LITERAL:
                            _addOutput();
                            break;
                    }
                // 开闭标签之外的内容
                } else {
                    _addOutput();
                }
        }

        // 调试模式下,添加编译的所在行数,输出错误提示时所用
        if (self.opts.compileDebug && newLineCount) {
            this.currentLine += newLineCount;
            this.source += '    ; __line = ' + this.currentLine + '\n';
        }
    }
};

// html转义
exports.escapeXML = utils.escapeXML;

// 通过模板路径和模板数据获取编译结果,并执行回调
exports.__express = exports.renderFile;

// node环境下,添加require方法加载扩展名为ejs的模块,值为编译函数
if (require.extensions) {
    require.extensions['.ejs'] = function (module, flnm) {
        var filename = flnm || /* istanbul ignore next */ module.filename;
        var options = {
            filename: filename,
            client: true// 同ejs.js不在同一个模块,escape、include以字符串形式赋给编译函数体
        };
        var template = fs.readFileSync(filename).toString();
        var fn = exports.compile(template, options);
        module._compile('module.exports = ' + fn.toString() + ';', filename);
    };
}

exports.VERSION = _VERSION_STRING;

exports.name = _NAME;

if (typeof window != 'undefined') {
    window.ejs = exports;
}

 

2.utils.js

/*
 * EJS Embedded JavaScript templates
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

/**
 * Private utility functions
 * @module utils
 * @private
 */

'use strict';

// 用户设置开启或者闭合尖括号所用的字符delimiter时,以下字符串有效
var regExpChars = /[|\\{}()[\]^$+*?.]/g;

// 用户设置开启或者闭合尖括号所用的字符delimiter时,仅|\{}()[]^$+*?.有效
exports.escapeRegExpChars = function (string) {
    if (!string) {
        return '';
    }
    return String(string).replace(regExpChars, '\\$&');// 输出匹配部分
};

var _ENCODE_HTML_RULES = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&#34;',
    "'": '&#39;'
};
var _MATCH_HTML = /[&<>\'"]/g;

function encode_char(c) {
    return _ENCODE_HTML_RULES[c] || c;
}

var escapeFuncStr =
  'var _ENCODE_HTML_RULES = {\n'
+ '      "&": "&amp;"\n'
+ '    , "<": "&lt;"\n'
+ '    , ">": "&gt;"\n'
+ '    , \'"\': "&#34;"\n'
+ '    , "\'": "&#39;"\n'
+ '    }\n'
+ '  , _MATCH_HTML = /[&<>\'"]/g;\n'
+ 'function encode_char(c) {\n'
+ '  return _ENCODE_HTML_RULES[c] || c;\n'
+ '};\n';

// html转义
exports.escapeXML = function (markup) {
    return markup == undefined ? '' : String(markup).replace(_MATCH_HTML, encode_char);
};

// 函数转化成字符串时,同时将参数变量全部转化为字符串输入
exports.escapeXML.toString = function () {
    return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr;
};

// 将from的属性或方法浅拷贝给to
exports.shallowCopy = function (to, from) {
    from = from || {};
    for (var p in from) {
        to[p] = from[p];
    }
    return to;
};

// list以数组形式约定从from浅拷贝给to的属性名或方法名
exports.shallowCopyFromList = function (to, from, list) {
    list.forEach(function (p) {
        if (typeof from[p] != 'undefined') {
            to[p] = from[p];
        }
    });
    return to;
};

// 缓存编译函数
exports.cache = {
    _data: {},
    set: function (key, val) {
        this._data[key] = val;
    },
    get: function (key) {
        return this._data[key];
    },
    reset: function () {
        this._data = {};
    }
};

 

0
0
分享到:
评论

相关推荐

    浅析嵌入式 JS 模板引擎之EJS

    EJS后缀名为”ejs” EJS 支持我们把JS代码直接写在标签内 EJS 能够缓存 JS函数的中间代码,使执行速度得到极大的提升 EJS 调错极其简单:因为它所有错误都是普通的 JS 异常,而且也会给我们输出异常发生的具体位置 ...

    EJS Challenge Starting Files_SIMULINK_源码.zip

    标题中的"EJS Challenge Starting Files_SIMULINK_源码.zip"表明这是一个关于电子工程或软件开发的挑战项目,其中包含了SIMULINK的源码文件。SIMULINK是MATLAB的一个扩展,用于建立动态系统的模型,广泛应用于控制...

    arm926ejs.rar_ARM926EJS _visual c

    这个RAR压缩包包含的是与ARM926EJS相关的开发程序源码,已经过编译和仿真的验证,适用于使用Visual C进行编程的开发者。接下来,我们将深入探讨ARM926EJS内核、Visual C开发环境以及如何结合这两者进行嵌入式系统...

    仿简书的源码源码源码

    7. **模板引擎**:可能使用了Handlebars、Pug或EJS等模板引擎来动态渲染HTML,便于将数据和视图结合。 8. **响应式布局**:使用媒体查询(Media Queries)确保页面在不同屏幕尺寸下都能自适应显示,提供良好的...

    友源社区APP论坛源码+网站源码.zip

    5. **模板引擎**:为了提高页面的渲染效率,源码可能会使用EJS、Pug、Twig等模板引擎来分离视图和逻辑,帮助快速构建动态网页。 6. **API设计**:源码中的API接口设计遵循RESTful原则,用于客户端与服务器间的数据...

    仿CF官网网站源码

    6. **模板引擎**:如Smarty或EJS,用于将数据动态渲染到HTML中。 7. **SEO优化**:如元标签设置、URL结构优化,提升网站在搜索引擎中的排名。 对于初学者,可以通过阅读源码学习网页布局技巧、函数调用、事件处理等...

    ejs-src-0.9.9-3.tar.gz_ejs_embeddedjavascript_嵌入式 服务器

    1. **源码文件**:`ejs-0.9.9`目录下通常会有`.js`文件,这些是EJS的核心实现,包含解析、编译和执行EJS模板的函数。 2. **示例**:可能包含一些示例文件,展示如何在项目中集成和使用EJS,帮助开发者快速上手。 3...

    前端网站实例100套源码

    7. **模板引擎**:部分实例可能使用了诸如EJS、Pug或 Handlebars等模板引擎,这些工具可以帮助开发者分离视图和逻辑,使代码更加清晰。 8. **前端框架和库**:除了Bootstrap,还可能涉及到Vue.js、React或Angular等...

    基于HTML的个人博客网站设计源码

    本项目是基于HTML的个人博客网站设计源码,包含1043个文件,其中939个Markdown文件,34个Stylus文件,29个EJS文件,15个YAML文件,9个JavaScript文件,2个JSON文件,2个CSS文件,2个EOT文件,2个TTF文件和2个WOFF...

    Node.js-一个简单快速类似ejs的node.js的模板引擎

    通过阅读源码,我们可以深入理解其内部实现;查看示例,可以了解如何在实际项目中使用这个模板引擎;阅读文档,则有助于我们掌握其完整功能和最佳实践。 总之,选择一个适合的模板引擎对于Node.js开发至关重要,...

    黄道择吉js源码

    现代Web开发中,可能会用到React、Vue等前端框架,或者直接使用模板引擎如EJS、Pug等。 5. **事件监听**:为了让用户能够交互,源码会添加事件监听器,例如点击日历日期时显示该日期的黄道吉日信息。 6. **兼容性...

    基于Node.js+Express+EJS的完整外卖后台管理系统设计源码

    本项目为基于Node.js、Express和EJS技术栈构建的外卖后台管理系统源码,共包含2926个文件,涵盖2077个PNG图片、655个JavaScript文件、69个CSS文件等,支持多语言开发环境,包括JavaScript、CSS、PHP和Python。...

    基于Node.js+Express+Ejs+MongoDB+RequireJS的多用户自适应快速博客系统设计源码

    该项目是一款基于Node.js、Express、Ejs、MongoDB和RequireJS构建的多用户自适应快速博客系统源码。该系统包含164个文件,涵盖75个JavaScript文件、35个Ejs模板文件、19个CSS样式文件、12个PNG图片文件、5个GIF图片...

    基于EJS和Express的MySQL数据库后台管理系统设计源码

    本源码项目是基于EJS和Express的MySQL数据库后台管理系统设计,包含44个文件,主要使用JavaScript和CSS编程语言。该系统实现了基本的CRUD后台管理应用,包括用户界面和数据操作功能,适用于快速搭建后台管理系统的...

    yeahjs:一个小型,现代,快速的EJS模板库

    EJS(嵌入式JavaScript模板)的微型,现代,快速实现。 对几乎替代品, 有一些。 例子 &lt;&#37; for ( let word of locals . items ) { -% &gt; &lt; li&gt;&lt; %= word % &gt;&lt; / li &gt; &lt; % } - %&gt; import { compile...

    微信活动邀请函(星耀360源码)

    为了方便替换图片和文字,源码可能使用了模板引擎(如Handlebars、EJS等),通过变量和控制结构来动态生成HTML内容。 6. **数据绑定**: 数据绑定技术(如AngularJS的双向数据绑定)可以使界面与后台数据保持同步...

    鲁班H5页面生成工具源码

    4. **模板引擎**:源码可能包含模板引擎,如ejs、pug或handlebars,这些模板语言可以帮助开发者快速构造动态页面,通过变量和逻辑控制来实现内容的动态渲染。 5. **响应式设计**:为了适应不同设备和屏幕尺寸,H5...

    前端品优购网页项目源码

    8. **模板引擎**:如果项目使用了诸如Pug、EJS或Handlebars等模板引擎,它们可以帮助开发者更方便地组织HTML结构,并在其中嵌入动态数据。 9. **版本控制**:项目的源码可能通过Git进行版本控制,每个文件都有其...

    绝对精品的网络工作室源码_酷炫的建站公司源码

    4. **模板引擎**:为了实现动态页面渲染,源码可能会集成如Twig、ejs、Jinja2等模板引擎,将后端数据与前端视图结合,生成最终的网页。 5. **安全性**:考虑到网络工作室源码可能涉及用户登录、支付等敏感操作,...

    个人业务网站源码最新版

    2. **模板引擎**:为了方便内容动态插入和更新,源码可能使用了模板引擎(如Smarty、Jinja2或EJS),允许开发者将静态HTML与动态数据分离,提高开发效率。 3. **数据库集成**:个人业务网站通常需要用户管理、订单...

Global site tag (gtag.js) - Google Analytics