1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | var determine_imports = require('./determine-imports');
|
11 | var extend = require('node.extend');
|
12 | var fs = require('fs');
|
13 | var less = require('less');
|
14 | var mkdirp = require('mkdirp');
|
15 | var path = require('path');
|
16 | var url = require('url');
|
17 | var utilities = require('./utilities');
|
18 |
|
19 |
|
20 | var imports = {};
|
21 |
|
22 |
|
23 | var checkImports = function(path, next) {
|
24 | var nodes = imports[path];
|
25 |
|
26 | if (!nodes || !nodes.length) {
|
27 | return next();
|
28 | }
|
29 |
|
30 | var pending = nodes.length;
|
31 | var changed = [];
|
32 |
|
33 | nodes.forEach(function(imported){
|
34 | fs.stat(imported.path, function(err, stat) {
|
35 |
|
36 | if (err || !imported.mtime || stat.mtime > imported.mtime) {
|
37 | changed.push(imported.path);
|
38 | }
|
39 |
|
40 | --pending || next(changed);
|
41 | });
|
42 | });
|
43 | };
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | module.exports = less.middleware = function(source, options, parserOptions, compilerOptions){
|
49 |
|
50 | if (typeof source == 'object') {
|
51 | throw new Error('Please update your less-middleware usage: http://goo.gl/YnK8p0');
|
52 | }
|
53 |
|
54 |
|
55 | if (!source) {
|
56 | throw new Error('less.middleware() requires `source` directory');
|
57 | }
|
58 |
|
59 |
|
60 | options = extend(true, {
|
61 | debug: false,
|
62 | dest: source,
|
63 | force: false,
|
64 | once: false,
|
65 | pathRoot: null,
|
66 | postprocess: {
|
67 | css: function(css, req) { return css; }
|
68 | },
|
69 | preprocess: {
|
70 | less: function(src, req) { return src; },
|
71 | path: function(pathname, req) { return pathname; }
|
72 | },
|
73 | storeCss: function(pathname, css, next) {
|
74 | mkdirp(path.dirname(pathname), 511 , function(err){
|
75 | if (err) return next(err);
|
76 |
|
77 | fs.writeFile(pathname, css, 'utf8', next);
|
78 | });
|
79 | }
|
80 | }, options || {});
|
81 |
|
82 |
|
83 | parserOptions = extend(true, {
|
84 | dumpLineNumbers: 0,
|
85 | paths: [source],
|
86 | optimization: 0,
|
87 | relativeUrls: false
|
88 | }, parserOptions || {});
|
89 |
|
90 |
|
91 | compilerOptions = extend(true, {
|
92 | compress: 'auto',
|
93 | sourceMap: false,
|
94 | yuicompress: false
|
95 | }, compilerOptions || {});
|
96 |
|
97 |
|
98 | var log = (options.debug ? utilities.logDebug : utilities.log);
|
99 |
|
100 |
|
101 | var render = function(str, lessPath, cssPath, callback) {
|
102 | var parser = new less.Parser(extend({}, parserOptions, {
|
103 | filename: lessPath
|
104 | }));
|
105 |
|
106 | parser.parse(str, function(err, tree) {
|
107 | if(err) {
|
108 | return callback(err);
|
109 | }
|
110 |
|
111 | try {
|
112 | var css = tree.toCSS(extend({}, compilerOptions, {
|
113 | compress: (options.compress == 'auto' ? utilities.isCompressedPath(cssPath) : options.compress)
|
114 | }));
|
115 |
|
116 |
|
117 | imports[lessPath] = determine_imports(tree, lessPath, parserOptions.paths);
|
118 |
|
119 | callback(err, css);
|
120 | } catch(parseError) {
|
121 | callback(parseError, null);
|
122 | }
|
123 | });
|
124 | };
|
125 |
|
126 |
|
127 | return function(req, res, next) {
|
128 | if ('GET' != req.method.toUpperCase() && 'HEAD' != req.method.toUpperCase()) { return next(); }
|
129 |
|
130 | var pathname = url.parse(req.url).pathname;
|
131 |
|
132 |
|
133 | if (utilities.isValidPath(pathname)) {
|
134 | var cssPath = path.join(options.dest, pathname);
|
135 | var lessPath = path.join(source, utilities.maybeCompressedSource(pathname));
|
136 |
|
137 | if (options.pathRoot) {
|
138 | pathname = pathname.replace(options.dest, '');
|
139 | cssPath = path.join(options.pathRoot, options.dest, pathname);
|
140 | lessPath = path.join(options.pathRoot, source, utilities.maybeCompressedSource(pathname));
|
141 | }
|
142 |
|
143 |
|
144 | lessPath = options.preprocess.path(lessPath, req);
|
145 |
|
146 | log('pathname', pathname);
|
147 | log('source', lessPath);
|
148 | log('destination', cssPath);
|
149 |
|
150 |
|
151 | var error = function(err) {
|
152 | return next('ENOENT' == err.code ? null : err);
|
153 | };
|
154 |
|
155 | var compile = function() {
|
156 | fs.readFile(lessPath, 'utf8', function(err, lessSrc){
|
157 | if (err) {
|
158 | return error(err);
|
159 | }
|
160 |
|
161 | delete imports[lessPath];
|
162 |
|
163 | try {
|
164 | lessSrc = options.preprocess.less(lessSrc, req);
|
165 | render(lessSrc, lessPath, cssPath, function(err, css){
|
166 | if (err) {
|
167 | utilities.lessError(err);
|
168 | return next(err);
|
169 | }
|
170 |
|
171 |
|
172 | css = options.postprocess.css(css, req);
|
173 |
|
174 |
|
175 | options.storeCss(cssPath, css, next);
|
176 | });
|
177 | } catch (err) {
|
178 | utilities.lessError(err);
|
179 | return next(err);
|
180 | }
|
181 | });
|
182 | };
|
183 |
|
184 |
|
185 | if (options.force) {
|
186 | return compile();
|
187 | }
|
188 |
|
189 |
|
190 | if (!imports[lessPath]) {
|
191 | return compile();
|
192 | }
|
193 |
|
194 |
|
195 | if (options.once && imports[lessPath]) {
|
196 | return next();
|
197 | }
|
198 |
|
199 |
|
200 | fs.stat(lessPath, function(err, lessStats){
|
201 | if (err) {
|
202 | return error(err);
|
203 | }
|
204 |
|
205 | fs.stat(cssPath, function(err, cssStats){
|
206 |
|
207 | if (err) {
|
208 | if ('ENOENT' == err.code) {
|
209 | log('not found', cssPath);
|
210 |
|
211 |
|
212 | return compile();
|
213 | } else {
|
214 | return next(err);
|
215 | }
|
216 | } else if (lessStats.mtime > cssStats.mtime) {
|
217 |
|
218 | log('modified', cssPath);
|
219 |
|
220 | return compile();
|
221 | } else {
|
222 |
|
223 | checkImports(lessPath, function(changed){
|
224 | if(typeof changed != "undefined" && changed.length) {
|
225 | log('modified import', changed);
|
226 |
|
227 | return compile();
|
228 | }
|
229 |
|
230 | return next();
|
231 | });
|
232 | }
|
233 | });
|
234 | });
|
235 | } else {
|
236 | return next();
|
237 | }
|
238 | };
|
239 | };
|