Fixes #150 - Provide option to prevent writing compiled templates to disk

This commit is contained in:
Patrick Steele-Idem 2015-09-24 10:32:54 -06:00
parent f85cdcae9f
commit 370ac4b3ab
6 changed files with 238 additions and 25 deletions

View File

@ -19,12 +19,26 @@ var req = require; // Fool code inspectors used by client-side bundles
var nodePath = require('path');
var defaultOptions = {
/**
* Set of tag names that should automatically have whitespace preserved.
* Alternatively, if value is `true` then whitespace will be preserved
* for all tags.
*/
preserveWhitespace: {
'pre': true,
'textarea': true,
'script': true
},
/**
* Set of tag names that should be allowed to be rendered as a self-closing
* XML tag. A self-closing tag will only be rendered if the tag has no nested
* content. HTML doesn't allow self-closing tags so you should likely
* never use this.
*/
allowSelfClosing: {},
/**
* Set of tag names that should be rendered with a start tag only.
*/
startTagOnly: {
'img': true,
'br': true,
@ -33,7 +47,22 @@ var defaultOptions = {
'link': true,
'hr': true
},
checkUpToDate: true
/**
* If true, then the compiler will check the disk to see if a previously compiled
* template is the same age or newer than the source template. If so, the previously
* compiled template will be loaded. Otherwise, the template will be recompiled
* and saved to disk.
*
* If false, the template will always be recompiled. If `writeToDisk` is false
* then this option will be ignored.
*/
checkUpToDate: true,
/**
* If true (the default) then compiled templates will be written to disk. If false,
* compiled templates will not be written to disk (i.e., no `.marko.js` file will
* be generated)
*/
writeToDisk: true
};
if (process.env.MARKO_CLEAN === '' || process.env.MARKO_CLEAN === 'true') {

View File

@ -131,4 +131,100 @@ Example usage:
```javascript
var template = require('./template.marko');
template.stream({ name: 'Frank' }).pipe(process.stdout);
```
# require('marko/compiler')
## Methods
### createCompiler(path, options) : TemplateCompiler
Creates a compiler for a given template path and given options.
### compile(src, path, options, callback)
Compiles a template given the loaded template source, the file system path of the source template and options.
Currently, compilation is synchronous so the callback is optional. In the future, we may allow asynchronous
compilation.
The result will be the compiled JavaScript source code.
### compileFile(path, options, callback)
Compiles a template given the loaded template source, the file system path of the source template and options.
Currently, compilation is synchronous so the callback is optional. In the future, we may allow asynchronous
compilation.
The result will be the compiled JavaScript source code.
### getLastModified(path, options, callback)
Compiles a template given the loaded template source, the file system path of the source template and options.
Currently, this method is synchronous so the callback is optional. In the future, we may allow this method to be asynchronous.
Returns the last modified time as a number.
### clearCaches()
Clears any internal caches used by the compiler. Needed for hot-reloading.
## Properties
### defaultOptions
The default options used by the compiler. These options can be changed as shown in the following sample code:
```javascript
require('marko/compiler').defaultOptions.writeToDisk = false;
```
Default options:
```javascript
{
/**
* Set of tag names that should automatically have whitespace preserved.
* Alternatively, if value is `true` then whitespace will be preserved
* for all tags.
*/
preserveWhitespace: {
'pre': true,
'textarea': true,
'script': true
},
/**
* Set of tag names that should be allowed to be rendered as a self-closing
* XML tag. A self-closing tag will only be rendered if the tag has no nested
* content. HTML doesn't allow self-closing tags so you should likely
* never use this.
*/
allowSelfClosing: {},
/**
* Set of tag names that should be rendered with a start tag only.
*/
startTagOnly: {
'img': true,
'br': true,
'input': true,
'meta': true,
'link': true,
'hr': true
},
/**
* If true, then the compiler will check the disk to see if a previously compiled
* template is the same age or newer than the source template. If so, the previously
* compiled template will be loaded. Otherwise, the template will be recompiled
* and saved to disk.
*
* If false, the template will always be recompiled. If `writeToDisk` is false
* then this option will be ignored.
*/
checkUpToDate: true,
/**
* If true (the default) then compiled templates will be written to disk. If false,
* compiled templates will not be written to disk (i.e., no `.marko.js` file will
* be generated)
*/
writeToDisk: true
}
```

View File

@ -1,12 +1,12 @@
/*
* Copyright 2011 eBay Software Foundation
*
*
* 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.
@ -20,28 +20,43 @@ var fs = require('fs');
var fsReadOptions = { encoding: 'utf8' };
function compile(templatePath, markoCompiler, compilerOptions) {
var targetDir = path.dirname(templatePath);
var targetFile = templatePath + '.js';
var compiler = markoCompiler.createCompiler(templatePath, compilerOptions);
var isUpToDate = compiler.checkUpToDate(targetFile);
var writeToDisk = compilerOptions.writeToDisk
if (writeToDisk == null) {
writeToDisk = markoCompiler.defaultOptions.writeToDisk;
}
var templateSrc;
var compiledSrc;
if (isUpToDate) {
compiledSrc = fs.readFileSync(targetFile, fsReadOptions);
if (writeToDisk === false) {
// Don't write the compiled template to disk. Instead, load it
// directly from the compiled source using the internals of the
// Node.js module loading system.
templateSrc = fs.readFileSync(templatePath, fsReadOptions);
compiledSrc = compiler.compile(templateSrc);
} else {
var templateSrc = fs.readFileSync(templatePath, fsReadOptions);
compiledSrc = compiler.compile(templateSrc);
var targetDir = path.dirname(templatePath);
// Write to a temporary file and move it into place to avoid problems
// assocatiated with multiple processes write to teh same file. We only
// write the compiled source code to disk so that stack traces will
// be accurate.
var filename = path.basename(targetFile);
var tempFile = path.join(targetDir, '.' + process.pid + '.' + Date.now() + '.' + filename);
fs.writeFileSync(tempFile, compiledSrc, fsReadOptions);
fs.renameSync(tempFile, targetFile);
var targetFile = templatePath + '.js';
var isUpToDate = compiler.checkUpToDate(targetFile);
if (isUpToDate) {
compiledSrc = fs.readFileSync(targetFile, fsReadOptions);
} else {
templateSrc = fs.readFileSync(templatePath, fsReadOptions);
compiledSrc = compiler.compile(templateSrc);
// Write to a temporary file and move it into place to avoid problems
// assocatiated with multiple processes write to teh same file. We only
// write the compiled source code to disk so that stack traces will
// be accurate.
var filename = path.basename(targetFile);
var tempFile = path.join(targetDir, '.' + process.pid + '.' + Date.now() + '.' + filename);
fs.writeFileSync(tempFile, compiledSrc, fsReadOptions);
fs.renameSync(tempFile, targetFile);
}
}
// We attach a path to the compiled template so that hot reloading will work.
@ -51,7 +66,7 @@ function compile(templatePath, markoCompiler, compilerOptions) {
exports.install = function(options) {
options = options || {};
var compilerOptions = options.compilerOptions;
var compilerOptions = options.compilerOptions || {};
var extension = options.extension || '.marko';

View File

@ -45,7 +45,7 @@ function loadSource(templatePath, compiledSrc) {
return templateModule.exports;
}
module.exports = function load(templatePath) {
function loadFile(templatePath) {
templatePath = nodePath.resolve(cwd, templatePath);
var targetDir = nodePath.dirname(templatePath);
@ -67,6 +67,20 @@ module.exports = function load(templatePath) {
fs.renameSync(tempFile, targetFile);
return require(targetFile);
}
module.exports = function load(templatePath) {
if (markoCompiler.defaultOptions.writeToDisk === false) {
// Don't write the compiled template to disk. Instead, load it
// directly from the compiled source using the internals of the
// Node.js module loading system.
var compiler = markoCompiler.createCompiler(templatePath);
var templateSrc = fs.readFileSync(templatePath, fsReadOptions);
var compiledSrc = compiler.compile(templateSrc);
return loadSource(templatePath, compiledSrc);
} else {
return loadFile(templatePath);
}
};
module.exports.loadSource = loadSource;

View File

@ -5,6 +5,7 @@ var expect = require('chai').expect;
var nodePath = require('path');
var marko = require('../');
var through = require('through');
var fs = require('fs');
require('../node-require').install();
@ -203,7 +204,7 @@ describe('marko/api' , function() {
greeting: 'Greetings'
}
};
var output = template.renderSync(data)
var output = template.renderSync(data);
expect(output).to.equal('Greetings John!');
});
@ -259,7 +260,7 @@ describe('marko/api' , function() {
it('should allow a template to be loaded from a compiled JS module', function(done) {
// Load the JS file to ensure the hello.marko.js file is created
marko.load(nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko'))
marko.load(nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko'));
var templateModule = require(nodePath.join(__dirname, 'fixtures/templates/api-tests/hello.marko.js'));
@ -336,4 +337,61 @@ describe('marko/api' , function() {
stream);
});
it('should write compiled templates to disk by default when using the Node.js require extension', function() {
var compiledPath;
try {
var templatePath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko');
compiledPath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko.js');
var template = require(templatePath);
expect(fs.existsSync(compiledPath)).to.equal(true);
expect(template.renderSync({name: 'Frank'})).to.equal('Hello Frank!');
} finally {
fs.unlinkSync(compiledPath);
}
});
it('should write compiled templates to disk by default when using load', function() {
var compiledPath;
try {
var templatePath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko');
compiledPath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko.js');
var template = marko.load(templatePath);
expect(fs.existsSync(compiledPath)).to.equal(true);
expect(template.renderSync({name: 'Frank'})).to.equal('Hello Frank!');
} finally {
fs.unlinkSync(compiledPath);
}
});
it('should allow compiled templates to not be written to disk when using the Node.js require extension', function() {
require('../compiler').defaultOptions.writeToDisk = false;
try {
var templatePath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko');
var compiledPath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko.js');
var template = require(templatePath);
expect(fs.existsSync(compiledPath)).to.equal(false);
expect(template.render).to.be.a('function');
expect(template.renderSync({name: 'Frank'})).to.equal('Hello Frank!');
} finally {
require('../compiler').defaultOptions.writeToDisk = true;
}
});
it('should allow compiled templates to not be written to disk when using load', function() {
require('../compiler').defaultOptions.writeToDisk = false;
try {
var templatePath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko');
var compiledPath = nodePath.join(__dirname, 'fixtures/write-to-disk/template.marko.js');
var template = marko.load(templatePath);
expect(fs.existsSync(compiledPath)).to.equal(false);
expect(template.render).to.be.a('function');
expect(template.renderSync({name: 'Frank'})).to.equal('Hello Frank!');
} finally {
require('../compiler').defaultOptions.writeToDisk = true;
}
});
});

View File

@ -0,0 +1 @@
Hello ${data.name}!