mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
#78 Change compiled templates to export a loaded Template instance
This commit is contained in:
parent
2ddf263a16
commit
7c00085380
@ -491,7 +491,7 @@ TemplateBuilder.prototype = {
|
||||
} else {
|
||||
params = ['out'];
|
||||
}
|
||||
out.append('exports.create = function(__helpers) {\n');
|
||||
out.append('function create(__helpers) {\n');
|
||||
//Write out the static variables
|
||||
this.writer.flush();
|
||||
this._writeVars(this.staticVars, out, INDENT);
|
||||
@ -508,7 +508,10 @@ TemplateBuilder.prototype = {
|
||||
out.append('\n');
|
||||
}
|
||||
out.append(this.writer.getOutput());
|
||||
out.append(INDENT + '};\n}');
|
||||
// We generate code that assign a partially Template instance to module.exports
|
||||
// and then we fully initialize the Template instance. This was done to avoid
|
||||
// problems with circular dependencies.
|
||||
out.append(INDENT + '};\n}\n(module.exports = require("marko").c(__filename)).c(create);');
|
||||
return out.toString();
|
||||
},
|
||||
setTemplateName: function (templateName) {
|
||||
|
||||
@ -25,6 +25,10 @@ exports.enable = function() {
|
||||
// Patch the Template prototype to proxy all render methods...
|
||||
|
||||
Object.keys(Template.prototype).forEach(function(k) {
|
||||
if (k === 'c') {
|
||||
return;
|
||||
}
|
||||
|
||||
var v = Template.prototype[k];
|
||||
|
||||
if (typeof v === 'function') {
|
||||
@ -45,9 +49,9 @@ exports.enable = function() {
|
||||
});
|
||||
|
||||
|
||||
var oldLoad = runtime.load;
|
||||
var oldCreateTemplate = runtime.c;
|
||||
|
||||
runtime.load = function hotReloadLoad(path) {
|
||||
runtime.c = function hotReloadCreateTemplate(path) {
|
||||
if (!path) {
|
||||
throw new Error('Invalid path');
|
||||
}
|
||||
@ -62,7 +66,7 @@ exports.enable = function() {
|
||||
templatePath = templatePath.replace(/\.js$/, '');
|
||||
}
|
||||
|
||||
var template = oldLoad.apply(runtime, arguments);
|
||||
var template = oldCreateTemplate.apply(runtime, arguments);
|
||||
|
||||
// Store the current last modified with the template
|
||||
template.__hotReloadModifiedFlag = modifiedFlag;
|
||||
|
||||
@ -28,11 +28,8 @@ function compile(templatePath, markoCompiler, compilerOptions) {
|
||||
fs.renameSync(tempFile, targetFile);
|
||||
}
|
||||
|
||||
// The compiled output is for a CommonJS module. The code that we want to load should
|
||||
// actually export a loaded template instance so we append some additional code to
|
||||
// load the compiled template and assign it to `module.exports`. We also attach a
|
||||
// path to the compiled template so that hot reloading will work.
|
||||
return compiledSrc + '\nexports.path=__filename;\nmodule.exports = require("marko").load(exports);';
|
||||
// We attach a path to the compiled template so that hot reloading will work.
|
||||
return compiledSrc;
|
||||
}
|
||||
|
||||
exports.install = function(options) {
|
||||
|
||||
@ -24,6 +24,18 @@
|
||||
|
||||
// async-writer provides all of the magic to support asynchronous
|
||||
// rendering to a stream
|
||||
|
||||
|
||||
/**
|
||||
* Method is for internal usage only. This method
|
||||
* is invoked by code in a compiled Marko template and
|
||||
* it is used to create a new Template instance.
|
||||
* @private
|
||||
*/
|
||||
exports.c = function createTemplate(path) {
|
||||
return new Template(path);
|
||||
};
|
||||
|
||||
var asyncWriter = require('async-writer');
|
||||
|
||||
// helpers can the core set of utility methods
|
||||
@ -39,6 +51,8 @@ var Readable;
|
||||
var AsyncWriter = asyncWriter.AsyncWriter;
|
||||
var extend = require('raptor-util/extend');
|
||||
|
||||
|
||||
|
||||
exports.AsyncWriter = AsyncWriter;
|
||||
|
||||
var stream;
|
||||
@ -53,12 +67,25 @@ if (streamPath) {
|
||||
stream = require(streamPath);
|
||||
}
|
||||
|
||||
function Template( options) {
|
||||
this._ = null;
|
||||
function Template(path, func, options) {
|
||||
this.path = path;
|
||||
this._ = func;
|
||||
this.buffer = !options || options.buffer !== false;
|
||||
}
|
||||
|
||||
Template.prototype = {
|
||||
/**
|
||||
* Internal method to initialize a loaded template with a
|
||||
* given create function that was generated by the compiler.
|
||||
* Warning: User code should not depend on this method. For
|
||||
* internal usage only.
|
||||
*
|
||||
* @private
|
||||
* @param {Function(__helpers)} createFunc The function used to produce the render(data, out) function.
|
||||
*/
|
||||
c: function(createFunc) {
|
||||
this._ = createFunc(helpers);
|
||||
},
|
||||
renderSync: function(data) {
|
||||
var out = new AsyncWriter();
|
||||
out.sync();
|
||||
@ -192,9 +219,28 @@ if (stream) {
|
||||
require('raptor-util/inherit')(Readable, stream.Readable);
|
||||
}
|
||||
|
||||
function load(templatePath, options) {
|
||||
var cache = exports.cache;
|
||||
function createRenderProxy(template) {
|
||||
return function(data, out) {
|
||||
template._(data, out);
|
||||
};
|
||||
}
|
||||
|
||||
function initTemplate(rawTemplate, templatePath) {
|
||||
if (rawTemplate.render) {
|
||||
return rawTemplate;
|
||||
}
|
||||
|
||||
var createFunc = rawTemplate.create || rawTemplate;
|
||||
|
||||
var template = createFunc.loaded;
|
||||
if (!template) {
|
||||
template = createFunc.loaded = new Template(templatePath);
|
||||
template.c(createFunc);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
function load(templatePath, options) {
|
||||
if (!templatePath) {
|
||||
throw new Error('"templatePath" is required');
|
||||
}
|
||||
@ -202,42 +248,18 @@ function load(templatePath, options) {
|
||||
var template;
|
||||
|
||||
if (typeof templatePath === 'string') {
|
||||
template = cache[templatePath];
|
||||
if (!template) {
|
||||
// The template has not been loaded
|
||||
|
||||
// Cache the Template instance before actually loading and initializing
|
||||
// the compiled template. This allows circular dependencies since the
|
||||
// partially loaded Template instance will be found in the cache.
|
||||
template = cache[templatePath] = new Template(options);
|
||||
|
||||
// Now load the template to get access to the factory function that is used to produce
|
||||
// the actual compiled template function. We pass the helpers
|
||||
// as the first argument to the factory function to produce
|
||||
// the template rendering function
|
||||
template._ = loader(templatePath).create(helpers); // Load the template factory and invoke it
|
||||
}
|
||||
template = initTemplate(loader(templatePath), templatePath);
|
||||
} else if (templatePath.render) {
|
||||
template = templatePath;
|
||||
} else {
|
||||
// If the first argument is already a loaded template then just return it
|
||||
if (templatePath.render) {
|
||||
return templatePath;
|
||||
}
|
||||
template = initTemplate(templatePath);
|
||||
}
|
||||
|
||||
// Instead of a path, assume we got a compiled template module
|
||||
// We store the loaded template with the factory function that was
|
||||
// used to get access to the compiled template function
|
||||
template = templatePath._;
|
||||
if (!template) {
|
||||
// First put the partially loaded Template instance on the
|
||||
// the compiled template module before actually loading and
|
||||
// initializing the compiled template. This allows for circular
|
||||
// dependencies during template loading.
|
||||
template = templatePath._ = new Template(options);
|
||||
|
||||
// Now fully initialize the template by adding the needed render
|
||||
// function.
|
||||
template._ = templatePath.create(helpers);
|
||||
}
|
||||
if (options) {
|
||||
template = new Template(
|
||||
template.path,
|
||||
createRenderProxy(template),
|
||||
options);
|
||||
}
|
||||
|
||||
return template;
|
||||
|
||||
@ -6,6 +6,8 @@ var nodePath = require('path');
|
||||
var marko = require('../');
|
||||
var through = require('through');
|
||||
|
||||
require('../node-require').install();
|
||||
|
||||
describe('marko/api' , function() {
|
||||
|
||||
before(function() {
|
||||
@ -275,4 +277,21 @@ describe('marko/api' , function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow a template to be required', function(done) {
|
||||
var templatePath = nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko');
|
||||
var template = require(templatePath);
|
||||
template.render(
|
||||
{
|
||||
name: 'John'
|
||||
},
|
||||
function(err, output) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
expect(output).to.equal('Hello John!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
exports.create = function(__helpers) {
|
||||
function create(__helpers) {
|
||||
var str = __helpers.s,
|
||||
empty = __helpers.e,
|
||||
notEmpty = __helpers.ne,
|
||||
@ -13,4 +13,5 @@ exports.create = function(__helpers) {
|
||||
"name": "World"
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -1,4 +1,4 @@
|
||||
exports.create = function(__helpers) {
|
||||
function create(__helpers) {
|
||||
var str = __helpers.s,
|
||||
empty = __helpers.e,
|
||||
notEmpty = __helpers.ne;
|
||||
@ -6,4 +6,5 @@ exports.create = function(__helpers) {
|
||||
return function render(data, out) {
|
||||
out.w('Hello John & Suzy Invalid Entity: &b ; Valid Numeric Entity: "\nValid Hexadecimal Entity:\n¢');
|
||||
};
|
||||
}
|
||||
}
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -1,4 +1,4 @@
|
||||
exports.create = function(__helpers) {
|
||||
function create(__helpers) {
|
||||
var str = __helpers.s,
|
||||
empty = __helpers.e,
|
||||
notEmpty = __helpers.ne,
|
||||
@ -13,4 +13,5 @@ exports.create = function(__helpers) {
|
||||
str(data.missing) +
|
||||
'!');
|
||||
};
|
||||
}
|
||||
}
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -1,4 +1,4 @@
|
||||
exports.create = function(__helpers) {
|
||||
function create(__helpers) {
|
||||
var str = __helpers.s,
|
||||
empty = __helpers.e,
|
||||
notEmpty = __helpers.ne;
|
||||
@ -6,4 +6,5 @@ exports.create = function(__helpers) {
|
||||
return function render(data, out) {
|
||||
out.w('Hello John');
|
||||
};
|
||||
}
|
||||
}
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
61
test/hot-reload-test.js
Normal file
61
test/hot-reload-test.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
var chai = require('chai');
|
||||
chai.Assertion.includeStack = true;
|
||||
var expect = require('chai').expect;
|
||||
var nodePath = require('path');
|
||||
var marko = require('../');
|
||||
var fs = require('fs');
|
||||
|
||||
require('../node-require').install();
|
||||
|
||||
describe('marko/hot-reload' , function() {
|
||||
before(function() {
|
||||
require('../hot-reload').enable();
|
||||
require('../compiler').defaultOptions.checkUpToDate = false;
|
||||
});
|
||||
|
||||
it('should allow a required template to be hot reloaded', function() {
|
||||
|
||||
|
||||
var srcTemplatePath = nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko');
|
||||
var templateSrc = fs.readFileSync(srcTemplatePath, { encoding: 'utf8' });
|
||||
|
||||
var tempTemplatePath = nodePath.join(__dirname, 'temp/hello.marko');
|
||||
fs.writeFileSync(tempTemplatePath, templateSrc, { encoding: 'utf8' });
|
||||
|
||||
var template = require(tempTemplatePath);
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!');
|
||||
|
||||
fs.writeFileSync(tempTemplatePath, templateSrc + '!', { encoding: 'utf8' });
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!');
|
||||
|
||||
require('../hot-reload').handleFileModified(tempTemplatePath);
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!!');
|
||||
});
|
||||
|
||||
it('should allow a non-required template to be hot reloaded', function() {
|
||||
|
||||
|
||||
var srcTemplatePath = nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko');
|
||||
var templateSrc = fs.readFileSync(srcTemplatePath, { encoding: 'utf8' });
|
||||
|
||||
var tempTemplatePath = nodePath.join(__dirname, 'temp/hello2.marko');
|
||||
fs.writeFileSync(tempTemplatePath, templateSrc, { encoding: 'utf8' });
|
||||
|
||||
var template = marko.load(tempTemplatePath);
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!');
|
||||
|
||||
fs.writeFileSync(tempTemplatePath, templateSrc + '!', { encoding: 'utf8' });
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!');
|
||||
|
||||
require('../hot-reload').handleFileModified(tempTemplatePath);
|
||||
|
||||
expect(template.renderSync({ name: 'John' })).to.equal('Hello John!!');
|
||||
});
|
||||
|
||||
});
|
||||
103
test/legacy-compiled-test.js
Normal file
103
test/legacy-compiled-test.js
Normal file
@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
var chai = require('chai');
|
||||
chai.Assertion.includeStack = true;
|
||||
var expect = require('chai').expect;
|
||||
var nodePath = require('path');
|
||||
var marko = require('../');
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
require('../node-require').install();
|
||||
|
||||
describe('marko/legacy-compiled' , function() {
|
||||
before(function() {
|
||||
require('../compiler').defaultOptions.checkUpToDate = false;
|
||||
});
|
||||
|
||||
it('should allow an exports.create template to be loaded', function() {
|
||||
|
||||
var template = require('marko').load({
|
||||
create: function(__helpers) {
|
||||
var escapeXml = __helpers.x;
|
||||
|
||||
return function render(data, out) {
|
||||
out.w('Hello ' +
|
||||
escapeXml(data.name) +
|
||||
'!');
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
var output = template.renderSync({
|
||||
name: 'Frank'
|
||||
});
|
||||
|
||||
expect(output).to.equal('Hello Frank!');
|
||||
});
|
||||
|
||||
it('should only load an exports.create template once', function() {
|
||||
|
||||
var compiled = {
|
||||
create: function(__helpers) {
|
||||
var escapeXml = __helpers.x;
|
||||
|
||||
return function render(data, out) {
|
||||
out.w('Hello ' +
|
||||
escapeXml(data.name) +
|
||||
'!');
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var template1 = require('marko').load(compiled);
|
||||
|
||||
var output = template1.renderSync({ name: 'Frank' });
|
||||
expect(output).to.equal('Hello Frank!');
|
||||
|
||||
var template2 = require('marko').load(compiled);
|
||||
|
||||
expect(template1).to.equal(template2);
|
||||
});
|
||||
|
||||
it('should allow a module.exports = function create() {} template to be loaded', function() {
|
||||
|
||||
var template = require('marko').load(function create(__helpers) {
|
||||
var escapeXml = __helpers.x;
|
||||
|
||||
return function render(data, out) {
|
||||
out.w('Hello ' +
|
||||
escapeXml(data.name) +
|
||||
'!');
|
||||
};
|
||||
});
|
||||
|
||||
var output = template.renderSync({
|
||||
name: 'Frank'
|
||||
});
|
||||
|
||||
expect(output).to.equal('Hello Frank!');
|
||||
});
|
||||
|
||||
it('should only load a module.exports = function create() {} template once', function() {
|
||||
|
||||
var compiled = function create(__helpers) {
|
||||
var escapeXml = __helpers.x;
|
||||
|
||||
return function render(data, out) {
|
||||
out.w('Hello ' +
|
||||
escapeXml(data.name) +
|
||||
'!');
|
||||
};
|
||||
};
|
||||
|
||||
var template1 = require('marko').load(compiled);
|
||||
|
||||
var output = template1.renderSync({ name: 'Frank' });
|
||||
expect(output).to.equal('Hello Frank!');
|
||||
|
||||
var template2 = require('marko').load(compiled);
|
||||
|
||||
expect(template1).to.equal(template2);
|
||||
});
|
||||
|
||||
});
|
||||
1
test/temp/.gitignore
vendored
Normal file
1
test/temp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.marko
|
||||
Loading…
x
Reference in New Issue
Block a user