1 | /* |
---|
2 | * grunt-contrib-coffee |
---|
3 | * http://gruntjs.com/ |
---|
4 | * |
---|
5 | * Copyright (c) 2012 Eric Woroshow, contributors |
---|
6 | * Licensed under the MIT license. |
---|
7 | */ |
---|
8 | |
---|
9 | module.exports = function(grunt) { |
---|
10 | 'use strict'; |
---|
11 | |
---|
12 | var path = require('path'); |
---|
13 | var _ = grunt.util._; |
---|
14 | |
---|
15 | grunt.registerMultiTask('coffee', 'Compile CoffeeScript files into JavaScript', function() { |
---|
16 | |
---|
17 | var options = this.options({ |
---|
18 | bare: false, |
---|
19 | join: false, |
---|
20 | sourceMap: false, |
---|
21 | separator: grunt.util.linefeed |
---|
22 | }); |
---|
23 | |
---|
24 | grunt.verbose.writeflags(options, 'Options'); |
---|
25 | |
---|
26 | this.files.forEach(function (f) { |
---|
27 | var validFiles = removeInvalidFiles(f); |
---|
28 | |
---|
29 | if (options.sourceMap === true) { |
---|
30 | var paths = createOutputPaths(f.dest); |
---|
31 | writeFileAndMap(paths, compileWithMaps(validFiles, options, paths)); |
---|
32 | } else if (options.join === true) { |
---|
33 | writeFile(f.dest, concatInput(validFiles, options)); |
---|
34 | } else { |
---|
35 | writeFile(f.dest, concatOutput(validFiles, options)); |
---|
36 | } |
---|
37 | }); |
---|
38 | }); |
---|
39 | |
---|
40 | var isLiterate = function (ext) { |
---|
41 | return (ext === ".litcoffee" || ext === ".md"); |
---|
42 | }; |
---|
43 | |
---|
44 | var removeInvalidFiles = function(files) { |
---|
45 | return files.src.filter(function(filepath) { |
---|
46 | if (!grunt.file.exists(filepath)) { |
---|
47 | grunt.log.warn('Source file "' + filepath + '" not found.'); |
---|
48 | return false; |
---|
49 | } else { |
---|
50 | return true; |
---|
51 | } |
---|
52 | }); |
---|
53 | }; |
---|
54 | |
---|
55 | var createOutputPaths = function (destination) { |
---|
56 | var fileName = path.basename(destination, path.extname(destination)); |
---|
57 | return { |
---|
58 | dest: destination, |
---|
59 | destName: fileName, |
---|
60 | destDir: appendTrailingSlash(path.dirname(destination)), |
---|
61 | mapFileName: fileName + '.js.map' |
---|
62 | }; |
---|
63 | }; |
---|
64 | |
---|
65 | var appendTrailingSlash = function (path) { |
---|
66 | if (path.length > 0) { |
---|
67 | return path + '/'; |
---|
68 | } else { |
---|
69 | return path; |
---|
70 | } |
---|
71 | }; |
---|
72 | |
---|
73 | var compileWithMaps = function (files, options, paths) { |
---|
74 | if (!hasUniformExtensions(files)) { |
---|
75 | return; |
---|
76 | } |
---|
77 | |
---|
78 | var mapOptions, filepath; |
---|
79 | |
---|
80 | if (files.length > 1) { |
---|
81 | mapOptions = createOptionsForJoin(files, paths, options.separator); |
---|
82 | } else { |
---|
83 | mapOptions = createOptionsForFile(files[0], paths); |
---|
84 | filepath = files[0]; |
---|
85 | } |
---|
86 | |
---|
87 | options = _.extend({ |
---|
88 | generatedFile: path.basename(paths.dest), |
---|
89 | sourceRoot: mapOptions.sourceRoot, |
---|
90 | sourceFiles: mapOptions.sourceFiles |
---|
91 | }, options); |
---|
92 | |
---|
93 | var output = compileCoffee(mapOptions.code, options, filepath); |
---|
94 | appendFooter(output, paths); |
---|
95 | return output; |
---|
96 | }; |
---|
97 | |
---|
98 | var hasUniformExtensions = function(files) { |
---|
99 | // get all extensions for input files |
---|
100 | var ext = files.map(function (f) { |
---|
101 | return path.extname(f); |
---|
102 | }); |
---|
103 | |
---|
104 | if(_.uniq(ext).length > 1) { |
---|
105 | grunt.fail.warn('Join and sourceMap options require input files share the same extension (found '+_.uniq(ext).join(', ')+').'); |
---|
106 | return false; |
---|
107 | } else { |
---|
108 | return true; |
---|
109 | } |
---|
110 | }; |
---|
111 | |
---|
112 | var createOptionsForJoin = function (files, paths, separator) { |
---|
113 | var code = concatFiles(files, separator); |
---|
114 | var targetFileName = paths.destName + '.src.coffee'; |
---|
115 | grunt.file.write(paths.destDir + targetFileName, code); |
---|
116 | |
---|
117 | return { |
---|
118 | code: code, |
---|
119 | sourceFiles: [targetFileName], |
---|
120 | sourceRoot: '' |
---|
121 | }; |
---|
122 | }; |
---|
123 | |
---|
124 | var concatFiles = function (files, separator) { |
---|
125 | return files.map(function (filePath) { |
---|
126 | return grunt.file.read(filePath); |
---|
127 | }).join(grunt.util.normalizelf(separator)); |
---|
128 | }; |
---|
129 | |
---|
130 | var createOptionsForFile = function (file, paths) { |
---|
131 | return { |
---|
132 | code: grunt.file.read(file), |
---|
133 | sourceFiles: [path.basename(file)], |
---|
134 | sourceRoot: appendTrailingSlash(path.relative(paths.destDir, path.dirname(file))) |
---|
135 | }; |
---|
136 | }; |
---|
137 | |
---|
138 | var appendFooter = function (output, paths) { |
---|
139 | // Add sourceMappingURL to file footer |
---|
140 | output.js = output.js + '\n/*\n//@ sourceMappingURL=' + paths.mapFileName + '\n*/'; |
---|
141 | }; |
---|
142 | |
---|
143 | var concatInput = function (files, options) { |
---|
144 | if (!hasUniformExtensions(files)) { |
---|
145 | return; |
---|
146 | } |
---|
147 | |
---|
148 | var code = concatFiles(files, options.separator); |
---|
149 | return compileCoffee(code, options); |
---|
150 | }; |
---|
151 | |
---|
152 | var concatOutput = function(files, options) { |
---|
153 | return files.map(function(filepath) { |
---|
154 | var code = grunt.file.read(filepath); |
---|
155 | return compileCoffee(code, options, filepath); |
---|
156 | }).join(grunt.util.normalizelf(options.separator)); |
---|
157 | }; |
---|
158 | |
---|
159 | var compileCoffee = function(code, options, filepath) { |
---|
160 | options = _.clone(options); |
---|
161 | if(filepath) { |
---|
162 | options.filename = filepath; |
---|
163 | options.literate = isLiterate(path.extname(filepath)); |
---|
164 | } |
---|
165 | |
---|
166 | try { |
---|
167 | return require('coffee-script').compile(code, options); |
---|
168 | } catch (e) { |
---|
169 | if (e.location == null || |
---|
170 | e.location.first_column == null || |
---|
171 | e.location.first_line == null) { |
---|
172 | grunt.log.error('Got an unexpected exception ' + |
---|
173 | 'from the coffee-script compiler. ' + |
---|
174 | 'The original exception was: ' + |
---|
175 | e); |
---|
176 | grunt.log.error('(The coffee-script compiler should not raise *unexpected* exceptions. ' + |
---|
177 | 'You can file this error as an issue of the coffee-script compiler: ' + |
---|
178 | 'https://github.com/jashkenas/coffee-script/issues)'); |
---|
179 | } else { |
---|
180 | var firstColumn = e.location.first_column; |
---|
181 | var firstLine = e.location.first_line; |
---|
182 | var codeLine = code.split('\n')[firstLine]; |
---|
183 | var errorArrows = '\x1B[31m>>\x1B[39m '; |
---|
184 | var offendingCharacter; |
---|
185 | |
---|
186 | if (firstColumn < codeLine.length) { |
---|
187 | offendingCharacter = '\x1B[31m' + codeLine[firstColumn] + '\x1B[39m'; |
---|
188 | } else { |
---|
189 | offendingCharacter = ''; |
---|
190 | } |
---|
191 | |
---|
192 | grunt.log.error(e); |
---|
193 | grunt.log.error('In file: ' + filepath); |
---|
194 | grunt.log.error('On line: ' + firstLine); |
---|
195 | // Log erroneous line and highlight offending character |
---|
196 | // grunt.log.error trims whitespace so we have to use grunt.log.writeln |
---|
197 | grunt.log.writeln(errorArrows + codeLine.substring(0, firstColumn) + |
---|
198 | offendingCharacter + codeLine.substring(firstColumn + 1)); |
---|
199 | grunt.log.writeln(errorArrows + grunt.util.repeat(firstColumn, ' ') + |
---|
200 | '\x1B[31m^\x1B[39m '); |
---|
201 | } |
---|
202 | grunt.fail.warn('CoffeeScript failed to compile.'); |
---|
203 | } |
---|
204 | }; |
---|
205 | |
---|
206 | var writeFileAndMap = function(paths, output) { |
---|
207 | if (!output || output.js.length === 0) { |
---|
208 | warnOnEmptyFile(paths.dest); |
---|
209 | return; |
---|
210 | } |
---|
211 | |
---|
212 | writeFile(paths.dest, output.js); |
---|
213 | writeFile(paths.destDir + paths.mapFileName, output.v3SourceMap); |
---|
214 | }; |
---|
215 | |
---|
216 | var warnOnEmptyFile = function (path) { |
---|
217 | grunt.log.warn('Destination (' + path + ') not written because compiled files were empty.'); |
---|
218 | }; |
---|
219 | |
---|
220 | var writeFile = function (path, output) { |
---|
221 | if (output.length < 1) { |
---|
222 | warnOnEmptyFile(path); |
---|
223 | } else { |
---|
224 | grunt.file.write(path, output); |
---|
225 | grunt.log.writeln('File ' + path + ' created.'); |
---|
226 | } |
---|
227 | }; |
---|
228 | }; |
---|