1+ /*
2+ * grunt-contrib-handlebars
3+ * http://gruntjs.com/
4+ *
5+ * Copyright (c) 2016 Tim Branyen, contributors
6+ * Licensed under the MIT license.
7+ */
8+
9+ 'use strict' ;
10+ var chalk = require ( 'chalk' ) ;
11+ var nsdeclare = require ( 'nsdeclare' ) ;
12+
13+ module . exports = function ( grunt ) {
14+ var _ = grunt . util . _ ;
15+
16+ // content conversion for templates
17+ var defaultProcessContent = function ( content ) { return content ; } ;
18+
19+ // AST processing for templates
20+ var defaultProcessAST = function ( ast ) { return ast ; } ;
21+
22+ // filename conversion for templates
23+ var defaultProcessName = function ( name ) { return name ; } ;
24+
25+ // filename conversion for partials
26+ var defaultProcessPartialName = function ( filepath ) {
27+ var pieces = _ . last ( filepath . split ( '/' ) ) . split ( '.' ) ;
28+ var name = _ ( pieces ) . without ( _ . last ( pieces ) ) . join ( '.' ) ; // strips file extension
29+ if ( name . charAt ( 0 ) === '_' ) {
30+ name = name . substr ( 1 , name . length ) ; // strips leading _ character
31+ }
32+ return name ;
33+ } ;
34+
35+ var extractGlobalNamespace = function ( nsDeclarations ) {
36+ // Extract global namespace from any existing namespace declaration.
37+ // The purpose of this method is too fix an issue with AMD when using namespace as a function where the
38+ // nsInfo.namespace will contains the last namespace, not the global namespace.
39+
40+ var declarations = _ . keys ( nsDeclarations ) ;
41+
42+ // no declaration found
43+ if ( ! declarations . length ) {
44+ return '' ;
45+ }
46+
47+ // In case only one namespace has been declared it will only return it.
48+ if ( declarations . length === 1 ) {
49+ return declarations [ 0 ] ;
50+ }
51+ // We only need to take any declaration to extract the global namespace.
52+ // Another option might be find the shortest declaration which is the global one.
53+ var matches = declarations [ 0 ] . match ( / ( t h i s \[ [ ^ \[ ] + \] ) / g) ;
54+ return matches [ 0 ] ;
55+ } ;
56+
57+ grunt . registerMultiTask ( 'handlebars' , 'Compile handlebars templates and partials.' , function ( ) {
58+ var options = this . options ( {
59+ namespace : 'JST' ,
60+ separator : grunt . util . linefeed + grunt . util . linefeed ,
61+ wrapped : true ,
62+ amd : false ,
63+ commonjs : false ,
64+ knownHelpers : [ ] ,
65+ knownHelpersOnly : false
66+ } ) ;
67+
68+ // assign regex for partials directory detection
69+ var partialsPathRegex = options . partialsPathRegex || / ./ ;
70+
71+ // assign regex for partial detection
72+ var isPartialRegex = options . partialRegex || / ^ _ / ;
73+
74+ // assign transformation functions
75+ var processContent = options . processContent || defaultProcessContent ;
76+ var processName = options . processName || defaultProcessName ;
77+ var processPartialName = options . processPartialName || defaultProcessPartialName ;
78+ var processAST = options . processAST || defaultProcessAST ;
79+ var useNamespace = options . namespace !== false ;
80+
81+ // assign compiler options
82+ var compilerOptions = options . compilerOptions || { } ;
83+ var filesCount = 0 ;
84+
85+ this . files . forEach ( function ( f ) {
86+ var declarations = [ ] ;
87+ var partials = { } ;
88+ var templates = { } ;
89+ // template identifying parts
90+ var ast , compiled , filename ;
91+
92+ // Namespace info for current template
93+ var nsInfo ;
94+
95+ // Map of already declared namespace parts
96+ var nsDeclarations = { } ;
97+
98+ // nsdeclare options when fetching namespace info
99+ var nsDeclareOptions = { response : 'details' , declared : nsDeclarations } ;
100+
101+ // Just get the namespace info for a given template
102+ var getNamespaceInfo = _ . memoize ( function ( filepath ) {
103+ if ( ! useNamespace ) {
104+ return undefined ;
105+ }
106+ if ( _ . isFunction ( options . namespace ) ) {
107+ return nsdeclare ( options . namespace ( filepath ) , nsDeclareOptions ) ;
108+ }
109+ return nsdeclare ( options . namespace , nsDeclareOptions ) ;
110+ } ) ;
111+
112+ // iterate files, processing partials and templates separately
113+ f . src . filter ( function ( filepath ) {
114+ // Warn on and remove invalid source files (if nonull was set).
115+ if ( ! grunt . file . exists ( filepath ) ) {
116+ grunt . log . warn ( 'Source file "' + filepath + '" not found.' ) ;
117+ return false ;
118+ }
119+ return true ;
120+ } )
121+ . forEach ( function ( filepath ) {
122+ var src = processContent ( grunt . file . read ( filepath ) , filepath ) ;
123+
124+ var Handlebars = require ( 'handlebars' ) ;
125+ try {
126+ // parse the handlebars template into it's AST
127+ ast = processAST ( Handlebars . parse ( src ) ) ;
128+ compiled = Handlebars . precompile ( ast , compilerOptions ) ;
129+
130+ // if configured to, wrap template in Handlebars.template call
131+ if ( options . wrapped === true ) {
132+ compiled = 'Handlebars.template(' + compiled + ')' ;
133+ }
134+ } catch ( e ) {
135+ grunt . log . error ( e ) ;
136+ grunt . fail . warn ( 'Handlebars failed to compile ' + filepath + '.' ) ;
137+ }
138+
139+ // register partial or add template to namespace
140+ if ( partialsPathRegex . test ( filepath ) && isPartialRegex . test ( _ . last ( filepath . split ( '/' ) ) ) ) {
141+ filename = processPartialName ( filepath ) ;
142+ if ( options . partialsUseNamespace === true ) {
143+ nsInfo = getNamespaceInfo ( filepath ) ;
144+ if ( nsInfo . declaration ) {
145+ declarations . push ( nsInfo . declaration ) ;
146+ }
147+ partials [ nsInfo . namespace + ':' + JSON . stringify ( filename ) ] = ( 'Handlebars.registerPartial(' +
148+ JSON . stringify ( filename ) + ', ' + nsInfo . namespace + '[' + JSON . stringify ( filename ) + '] = ' +
149+ compiled + ');' ) ;
150+ } else {
151+ partials [ JSON . stringify ( filename ) ] = ( 'Handlebars.registerPartial(' + JSON . stringify ( filename ) +
152+ ', ' + compiled + ');' ) ;
153+ }
154+ } else {
155+ if ( ( options . amd || options . commonjs ) && ! useNamespace ) {
156+ compiled = 'return ' + compiled ;
157+ }
158+ filename = processName ( filepath ) ;
159+ if ( useNamespace ) {
160+ nsInfo = getNamespaceInfo ( filepath ) ;
161+ if ( nsInfo . declaration ) {
162+ declarations . push ( nsInfo . declaration ) ;
163+ }
164+ templates [ nsInfo . namespace + ':' + JSON . stringify ( filename ) ] = ( nsInfo . namespace + '[' +
165+ JSON . stringify ( filename ) + '] = ' + compiled + ';' ) ;
166+ } else if ( options . commonjs === true ) {
167+ templates [ JSON . stringify ( filename ) ] = compiled + ';' ;
168+ } else {
169+ templates [ JSON . stringify ( filename ) ] = compiled ;
170+ }
171+ }
172+ } ) ;
173+
174+ var output = declarations . concat ( _ . values ( partials ) , _ . values ( templates ) ) ;
175+ if ( output . length < 1 ) {
176+ grunt . log . warn ( 'Destination not written because compiled files were empty.' ) ;
177+ } else {
178+ if ( useNamespace ) {
179+ if ( options . node ) {
180+ output . unshift ( 'Handlebars = glob.Handlebars || require(\'handlebars\');' ) ;
181+ output . unshift ( 'var glob = (\'undefined\' === typeof window) ? global : window,' ) ;
182+
183+ var nodeExport = 'if (typeof exports === \'object\' && exports) {' ;
184+ nodeExport += 'module.exports = ' + nsInfo . namespace + ';}' ;
185+
186+ output . push ( nodeExport ) ;
187+ }
188+
189+ }
190+
191+ if ( options . amd ) {
192+ // Wrap the file in an AMD define fn.
193+ if ( typeof options . amd === 'boolean' ) {
194+ output . unshift ( 'define([\'handlebars\'], function(Handlebars) {' ) ;
195+ } else if ( typeof options . amd === 'string' ) {
196+ output . unshift ( 'define([\'' + options . amd + '\'], function(Handlebars) {' ) ;
197+ } else if ( typeof options . amd === 'function' ) {
198+ output . unshift ( 'define([\'' + options . amd ( filename , ast , compiled ) + '\'], function(Handlebars) {' ) ;
199+ } else if ( Array . isArray ( options . amd ) ) {
200+ // convert options.amd to a string of dependencies for require([...])
201+ var amdString = '' ;
202+ for ( var i = 0 ; i < options . amd . length ; i ++ ) {
203+ if ( i !== 0 ) {
204+ amdString += ', ' ;
205+ }
206+
207+ amdString += '\'' + options . amd [ i ] + '\'' ;
208+ }
209+
210+ // Wrap the file in an AMD define fn.
211+ output . unshift ( 'define([' + amdString + '], function(Handlebars) {' ) ;
212+ }
213+
214+ if ( useNamespace ) {
215+ // Namespace has not been explicitly set to false; the AMD
216+ // wrapper will return the object containing the template.
217+ output . push ( 'return ' + extractGlobalNamespace ( nsDeclarations ) + ';' ) ;
218+ }
219+ output . push ( '});' ) ;
220+ }
221+
222+ if ( options . commonjs ) {
223+ if ( useNamespace ) {
224+ output . push ( 'return ' + nsInfo . namespace + ';' ) ;
225+ }
226+ // Export the templates object for CommonJS environments.
227+ output . unshift ( 'module.exports = function(Handlebars) {' ) ;
228+ output . push ( '};' ) ;
229+ }
230+
231+ filesCount ++ ;
232+ grunt . file . write ( f . dest , output . join ( grunt . util . normalizelf ( options . separator ) ) ) ;
233+ grunt . verbose . writeln ( 'File ' + chalk . cyan ( f . dest ) + ' created.' ) ;
234+ }
235+ } ) ;
236+
237+ grunt . log . ok ( filesCount + ' ' + grunt . util . pluralize ( filesCount , 'file/files' ) + ' created.' ) ;
238+ } ) ;
239+ } ;
0 commit comments