diff --git a/lib/page.js b/lib/page.js index da59bf9..97c0569 100644 --- a/lib/page.js +++ b/lib/page.js @@ -127,6 +127,9 @@ var Page = function( page_path, options ){ } } } + + params.partials = page.findPartials(data) + page.params = params; _( page ).extend({ title: params.title, @@ -134,6 +137,7 @@ var Page = function( page_path, options ){ name: params.name, layout: params.layout }); + if( callback ) callback( params ); } @@ -141,6 +145,23 @@ var Page = function( page_path, options ){ }; + // finds the names of the partials used by the template + this.findPartials = function(template) { + var templateWithoutComments = template.replace(/{{!--[\s\S]*?--}}/g, '').replace(/{{![\s\S]*?}}/g, '') + var partials = [] + var partialRegex = /{{>\s*([^\s}]+)[\s\S]*?}}/g + var match + while (match = partialRegex.exec(templateWithoutComments)) { + var partial = match[1] + if (partial[0] == '"' || partial[0] == "'") { + // '"quoted\\"partial"' -> 'quoted"partial' + partial = eval(partial) + } + partials.push(server.viewPath(partial)) + } + return partials + } + // fetches remote resources this.fetchResources = function( resources, params, iterator, callback ){ @@ -270,19 +291,11 @@ var Page = function( page_path, options ){ }; // preprocesses the page's context - this.preprocess = function( context, callback ){ - - var preprocessor = server.preprocessors[page.params.preprocessor]; - - if( preprocessor ){ - preprocessor.process( context, function( processed_context ){ - if( callback ) callback( processed_context ); - }); - } - else { - if( callback ) callback( context ); - } - + this.preprocess = function(context, callback) { + async.reduce(this.allPreprocessors(), context, function(context, preprocessor, callback) { + server.logger.log('Running preprocessor: ' + path.relative(server.paths.preprocessors, preprocessor.path), 3); + preprocessor.process(context, callback) + }, callback) }; // generates the page's markup @@ -344,14 +357,14 @@ var Page = function( page_path, options ){ }, DEFAULT_PAGE_TIMEOUT ); if( server.options.log_level >= 2 ) var start_resources = new Date; - this.fetchResources( page.params.resources, context.parameters, + this.fetchResources( this.allResources(), context.parameters, function(resource, response) { context.resources[resource.name] = response; }, function() { server.logger.log( page.route +' resources fetched in '+ ( new Date - start_resources ) +'ms', 3 ); if( server.options.log_level >= 2 ) var start_preprocess = new Date; - page.preprocess( context, function( context ){ + page.preprocess( context, function( err, context ){ server.logger.log( page.route +' preprocessed in '+ ( new Date - start_preprocess ) +'ms', 3 ); renderPage( context ); }); @@ -360,6 +373,29 @@ var Page = function( page_path, options ){ }; + this.allResources = function() { + var resources = page.params.resources || {} + this.getPartialsPages().forEach(function(partial) { + resources = _.extend(partial.allResources(), resources) + }) + return resources + } + + this.allPreprocessors = function() { + var preprocessors = _.compact([server.preprocessors[page.params.preprocessor]]) + this.getPartialsPages().forEach(function(partial) { + preprocessors = preprocessors.concat(partial.allPreprocessors()) + }) + return preprocessors + } + + this.getPartialsPages = function() { + var pages = page.params.partials.map(function(partial) { + return server.views[partial] + }) + return _.compact(pages) + } + // get the view's layout this.getLayout = function(){ diff --git a/lib/preprocessor.js b/lib/preprocessor.js index 54b1cf2..2adfdc6 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -37,9 +37,9 @@ var Preprocessor = function( preprocessor_path, options ){ } }); } - if( callback ) return callback( context ); + if( callback ) return callback( null, context ); } - if( callback ) return callback( preprocessed_context ); + if( callback ) return callback( null, preprocessed_context ); }); }; diff --git a/lib/server.js b/lib/server.js index 5586969..3d466ed 100644 --- a/lib/server.js +++ b/lib/server.js @@ -172,7 +172,7 @@ var SolidusServer = function( options ){ // creates pages for every view this.setupViews = function(){ - glob( paths.views +'/**/*.'+ DEFAULT_VIEW_EXTENSION, function( err, view_paths ){ + glob( this.viewPath('**/*'), function( err, view_paths ){ view_paths = view_paths.map( deGlobifyPath ); async.each( view_paths, solidus_server.addView, function( err ){ solidus_server.emit('ready'); @@ -181,6 +181,10 @@ var SolidusServer = function( options ){ }; + this.viewPath = function(partial_name) { + return paths.views + '/' + partial_name + '.' + DEFAULT_VIEW_EXTENSION + }; + // loads global auth this.setupAuth = function(){ diff --git a/test/fixtures/site 1/preprocessors/page_with_resources_and_partials.js b/test/fixtures/site 1/preprocessors/page_with_resources_and_partials.js new file mode 100644 index 0000000..8a7d083 --- /dev/null +++ b/test/fixtures/site 1/preprocessors/page_with_resources_and_partials.js @@ -0,0 +1,5 @@ +module.exports = function( context ){ + context.preprocessedBy = context.preprocessedBy || [] + context.preprocessedBy.push('page_with_resources_and_partials.js') + return context; +}; \ No newline at end of file diff --git a/test/fixtures/site 1/preprocessors/partial1_with_resources.js b/test/fixtures/site 1/preprocessors/partial1_with_resources.js new file mode 100644 index 0000000..019746f --- /dev/null +++ b/test/fixtures/site 1/preprocessors/partial1_with_resources.js @@ -0,0 +1,5 @@ +module.exports = function( context ){ + context.preprocessedBy = context.preprocessedBy || [] + context.preprocessedBy.push('partial1_with_resources.js') + return context; +}; \ No newline at end of file diff --git a/test/fixtures/site 1/preprocessors/partial2_with_resources.js b/test/fixtures/site 1/preprocessors/partial2_with_resources.js new file mode 100644 index 0000000..5e20551 --- /dev/null +++ b/test/fixtures/site 1/preprocessors/partial2_with_resources.js @@ -0,0 +1,5 @@ +module.exports = function( context ){ + context.preprocessedBy = context.preprocessedBy || [] + context.preprocessedBy.push('partial2_with_resources.js') + return context; +}; \ No newline at end of file diff --git a/test/fixtures/site 1/views/multiple_partials.hbs b/test/fixtures/site 1/views/multiple_partials.hbs new file mode 100644 index 0000000..bc69e73 --- /dev/null +++ b/test/fixtures/site 1/views/multiple_partials.hbs @@ -0,0 +1,37 @@ +{{! +{ + "title": "test", +} +}} + +{{>partial1}} +{{>partial1}} +{{>partial1 some-context}} + +{{> partial2 }} +{{> partial2 some-context }} + +{{> +partial3 +}} +{{> +partial3 +some-context +}} + +{{>partial/4}} + +{{!{{>partial5}}}} +{{! + {{>partial6}} +}} +{{!--{{>partial7}}--}} +{{!-- + {{>partial8}} +--}} + +{{>'partial9'}} +{{>'partial\'10'}} + +{{>"partial11"}} +{{>"partial\"12"}} diff --git a/test/fixtures/site 1/views/page_with_resources_and_partials.hbs b/test/fixtures/site 1/views/page_with_resources_and_partials.hbs new file mode 100644 index 0000000..a09f4a0 --- /dev/null +++ b/test/fixtures/site 1/views/page_with_resources_and_partials.hbs @@ -0,0 +1,12 @@ +{{! +{ + "title": "test", + "resources": { + "page-resource": "https://solid.us/basic/1" + }, + "preprocessor": "page_with_resources_and_partials.js" +} +}} + +{{>partial1_with_resources}} +{{>partial2_with_resources}} diff --git a/test/fixtures/site 1/views/partial1_with_resources.hbs b/test/fixtures/site 1/views/partial1_with_resources.hbs new file mode 100644 index 0000000..29caf14 --- /dev/null +++ b/test/fixtures/site 1/views/partial1_with_resources.hbs @@ -0,0 +1,12 @@ +{{! +{ + "title": "partial1", + "resources": { + "page-resource": "https://solid.us/invalid", + "partial1-resource": "https://solid.us/basic/2" + }, + "preprocessor": "partial1_with_resources.js" +} +}} + +{{>partial2_with_resources}} diff --git a/test/fixtures/site 1/views/partial2_with_resources.hbs b/test/fixtures/site 1/views/partial2_with_resources.hbs new file mode 100644 index 0000000..f6bc0dc --- /dev/null +++ b/test/fixtures/site 1/views/partial2_with_resources.hbs @@ -0,0 +1,9 @@ +{{! +{ + "title": "partial2", + "resources": { + "partial2-resource": "https://solid.us/basic/2" + }, + "preprocessor": "partial2_with_resources.js" +} +}} diff --git a/test/solidus.js b/test/solidus.js index 56ff3d9..86130e3 100644 --- a/test/solidus.js +++ b/test/solidus.js @@ -69,17 +69,17 @@ describe( 'Solidus', function(){ Page.cache.reset(); // mock http endpoints for resources - nock('https://solid.us').get('/basic/1').reply( 200, { test: true } ); - nock('https://solid.us').get('/basic/2').reply( 200, { test: true } ); - nock('https://solid.us').get('/dynamic/segment/3').reply( 200, { test: true } ); - nock('https://solid.us').get('/resource/options/url').reply( 200, { test: true } ); - nock('https://solid.us').get('/resource/options/query?test=true').reply( 200, { test: true } ); - nock('https://solid.us').get('/resource/options/dynamic/query?test=3').reply( 200, { test: true } ); - nock('https://solid.us').get('/resource/options/double/dynamic/query?test2=4&test=3').reply( 200, { test: true } ); - nock('https://solid.us').get('/centralized/auth/query').reply( 200, { test: true } ); - nock('https://solid.us').get('/resource/options/headers').matchHeader( 'key', '12345' ).reply( 200, { test: true } ); - nock('https://a.solid.us').get('/centralized/auth').matchHeader( 'key', '12345' ).reply( 200, { test: true } ); - nock('https://b.solid.us').get('/centralized/auth/query?key=12345').reply( 200, { test: true } ); + nock('https://solid.us').get('/basic/1').reply( 200, { test: '/basic/1' } ); + nock('https://solid.us').get('/basic/2').times(2).reply( 200, { test: '/basic/2' } ); + nock('https://solid.us').get('/dynamic/segment/3').reply( 200, { test: '/dynamic/segment/3' } ); + nock('https://solid.us').get('/resource/options/url').reply( 200, { test: '/resource/options/url' } ); + nock('https://solid.us').get('/resource/options/query?test=true').reply( 200, { test: '/resource/options/query?test=true' } ); + nock('https://solid.us').get('/resource/options/dynamic/query?test=3').reply( 200, { test: '/resource/options/dynamic/query?test=3' } ); + nock('https://solid.us').get('/resource/options/double/dynamic/query?test2=4&test=3').reply( 200, { test: '/resource/options/double/dynamic/query?test2=4&test=3' } ); + nock('https://solid.us').get('/centralized/auth/query').reply( 200, { test: '/centralized/auth/query' } ); + nock('https://solid.us').get('/resource/options/headers').matchHeader( 'key', '12345' ).reply( 200, { test: '/resource/options/headers' } ); + nock('https://a.solid.us').get('/centralized/auth').matchHeader( 'key', '12345' ).reply( 200, { test: '/centralized/auth' } ); + nock('https://b.solid.us').get('/centralized/auth/query?key=12345').reply( 200, { test: '/centralized/auth/query?key=12345' } ); // empty dynamic segments nock('https://solid.us').get('/dynamic/segment/').reply( 200, { test: false } ); nock('https://solid.us').get('/resource/options/dynamic/query?test=').reply( 200, { test: false } ); @@ -88,13 +88,13 @@ describe( 'Solidus', function(){ async.parallel([ // compressed resources function( callback ){ - zlib.gzip( '{"test":true}', function( _, result ){ + zlib.gzip( '{"test":"/compressed/gzip"}', function( _, result ){ nock('https://solid.us').get('/compressed/gzip').reply( 200, result, { 'Content-Encoding': 'gzip' } ); callback(); }); }, function( callback ){ - zlib.deflate( '{"test":true}', function( _, result ){ + zlib.deflate( '{"test":"/compressed/deflate"}', function( _, result ){ nock('https://solid.us').get('/compressed/deflate').reply( 200, result, { 'Content-Encoding': 'deflate' } ); callback(); }); @@ -231,6 +231,26 @@ describe( 'Solidus', function(){ }); }); + it( 'Finds the list of partials used by each page', function( done ){ + var dir = path.join(site1_path, 'views') + var partials = [ + path.join(dir, 'partial1.hbs'), + path.join(dir, 'partial1.hbs'), + path.join(dir, 'partial1.hbs'), + path.join(dir, 'partial2.hbs'), + path.join(dir, 'partial2.hbs'), + path.join(dir, 'partial3.hbs'), + path.join(dir, 'partial3.hbs'), + path.join(dir, 'partial/4.hbs'), + path.join(dir, 'partial9.hbs'), + path.join(dir, "partial'10.hbs"), + path.join(dir, 'partial11.hbs'), + path.join(dir, 'partial"12.hbs') + ] + assert.deepEqual(solidus_server.views[path.join(dir, 'multiple_partials.hbs')].params.partials, partials) + done() + }); + it( 'Fetches resources and adds them to the page context', function( done ){ var s_request = request( solidus_server.router ); async.parallel([ @@ -260,6 +280,30 @@ describe( 'Solidus', function(){ }); }); + it( 'Fetches partials resources and adds them to the page context', function( done ){ + var s_request = request( solidus_server.router ); + async.parallel([ + function( callback ){ + s_request.get('/page_with_resources_and_partials.json') + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end( function( err, res ){ + var resources = { + 'page-resource': {test: '/basic/1'}, + 'partial1-resource': {test: '/basic/2'}, + 'partial2-resource': {test: '/basic/2'} + } + assert.equal(res.body.page.title, 'test'); + assert.deepEqual(res.body.resources, resources); + callback( err ); + }); + } + ], function( err, results ){ + if( err ) throw err; + done(); + }); + }); + it( 'Preprocesses the context of pages', function( done ){ var s_request = request( solidus_server.router ); async.parallel([ @@ -293,6 +337,30 @@ describe( 'Solidus', function(){ }); }); + it( 'Preprocesses the context of partials', function( done ){ + var s_request = request( solidus_server.router ); + async.parallel([ + function( callback ){ + s_request.get('/page_with_resources_and_partials.json') + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end( function( err, res ){ + var preprocessedBy = [ + 'page_with_resources_and_partials.js', + 'partial1_with_resources.js', + 'partial2_with_resources.js', + 'partial2_with_resources.js' + ] + assert.deepEqual(res.body.preprocessedBy, preprocessedBy); + callback( err ); + }); + } + ], function( err, results ){ + if( err ) throw err; + done(); + }); + }); + it( 'Serves assets in /assets', function( done ){ var s_request = request( solidus_server.router ); async.parallel([