lessc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. #!/usr/bin/env node
  2. "use strict";
  3. var path = require('path');
  4. var fs = require('../lib/less-node/fs').default;
  5. var os = require('os');
  6. var utils = require('../lib/less/utils');
  7. var Constants = require('../lib/less/constants');
  8. var less = require('../lib/less-node').default;
  9. var errno;
  10. var mkdirp;
  11. try {
  12. errno = require('errno');
  13. } catch (err) {
  14. errno = null;
  15. }
  16. var pluginManager = new less.PluginManager(less);
  17. var fileManager = new less.FileManager();
  18. var plugins = [];
  19. var queuePlugins = [];
  20. var args = process.argv.slice(1);
  21. var silent = false;
  22. var verbose = false;
  23. var options = less.options;
  24. options.plugins = plugins;
  25. options.reUsePluginManager = true;
  26. var sourceMapOptions = {};
  27. var continueProcessing = true;
  28. var checkArgFunc = function checkArgFunc(arg, option) {
  29. if (!option) {
  30. console.error("".concat(arg, " option requires a parameter"));
  31. continueProcessing = false;
  32. process.exitCode = 1;
  33. return false;
  34. }
  35. return true;
  36. };
  37. var checkBooleanArg = function checkBooleanArg(arg) {
  38. var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
  39. if (!onOff) {
  40. console.error(" unable to parse ".concat(arg, " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no"));
  41. continueProcessing = false;
  42. process.exitCode = 1;
  43. return false;
  44. }
  45. return Boolean(onOff[2]);
  46. };
  47. var parseVariableOption = function parseVariableOption(option, variables) {
  48. var parts = option.split('=', 2);
  49. variables[parts[0]] = parts[1];
  50. };
  51. var sourceMapFileInline = false;
  52. function printUsage() {
  53. less.lesscHelper.printUsage();
  54. pluginManager.Loader.printUsage(plugins);
  55. continueProcessing = false;
  56. }
  57. function render() {
  58. if (!continueProcessing) {
  59. return;
  60. }
  61. var input = args[1];
  62. if (input && input != '-') {
  63. input = path.resolve(process.cwd(), input);
  64. }
  65. var output = args[2];
  66. var outputbase = args[2];
  67. if (output) {
  68. output = path.resolve(process.cwd(), output);
  69. }
  70. if (options.sourceMap) {
  71. sourceMapOptions.sourceMapInputFilename = input;
  72. if (!sourceMapOptions.sourceMapFullFilename) {
  73. if (!output && !sourceMapFileInline) {
  74. console.error('the sourcemap option only has an optional filename if the css filename is given');
  75. console.error('consider adding --source-map-map-inline which embeds the sourcemap into the css');
  76. process.exitCode = 1;
  77. return;
  78. } // its in the same directory, so always just the basename
  79. if (output) {
  80. sourceMapOptions.sourceMapOutputFilename = path.basename(output);
  81. sourceMapOptions.sourceMapFullFilename = "".concat(output, ".map");
  82. } // its in the same directory, so always just the basename
  83. if ('sourceMapFullFilename' in sourceMapOptions) {
  84. sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
  85. }
  86. } else if (options.sourceMap && !sourceMapFileInline) {
  87. var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename);
  88. var mapDir = path.dirname(mapFilename);
  89. var outputDir = path.dirname(output); // find the path from the map to the output file
  90. sourceMapOptions.sourceMapOutputFilename = path.join(path.relative(mapDir, outputDir), path.basename(output)); // make the sourcemap filename point to the sourcemap relative to the css file output directory
  91. sourceMapOptions.sourceMapFilename = path.join(path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
  92. }
  93. if (sourceMapOptions.sourceMapURL && sourceMapOptions.disableSourcemapAnnotation) {
  94. console.error('You cannot provide flag --source-map-url with --source-map-no-annotation.');
  95. console.error('Please remove one of those flags.');
  96. process.exitcode = 1;
  97. return;
  98. }
  99. }
  100. if (sourceMapOptions.sourceMapBasepath === undefined) {
  101. sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
  102. }
  103. if (sourceMapOptions.sourceMapRootpath === undefined) {
  104. var pathToMap = path.dirname((sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename) || '.');
  105. var pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename || '.');
  106. sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
  107. }
  108. if (!input) {
  109. console.error('lessc: no input files');
  110. console.error('');
  111. printUsage();
  112. process.exitCode = 1;
  113. return;
  114. }
  115. var ensureDirectory = function ensureDirectory(filepath) {
  116. var dir = path.dirname(filepath);
  117. var cmd;
  118. var existsSync = fs.existsSync || path.existsSync;
  119. if (!existsSync(dir)) {
  120. if (mkdirp === undefined) {
  121. try {
  122. mkdirp = require('make-dir');
  123. } catch (e) {
  124. mkdirp = null;
  125. }
  126. }
  127. cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
  128. cmd(dir);
  129. }
  130. };
  131. if (options.depends) {
  132. if (!outputbase) {
  133. console.error('option --depends requires an output path to be specified');
  134. process.exitCode = 1;
  135. return;
  136. }
  137. process.stdout.write("".concat(outputbase, ": "));
  138. }
  139. if (!sourceMapFileInline) {
  140. var writeSourceMap = function writeSourceMap() {
  141. var output = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  142. var onDone = arguments.length > 1 ? arguments[1] : undefined;
  143. var filename = sourceMapOptions.sourceMapFullFilename;
  144. ensureDirectory(filename);
  145. fs.writeFile(filename, output, 'utf8', function (err) {
  146. if (err) {
  147. var description = 'Error: ';
  148. if (errno && errno.errno[err.errno]) {
  149. description += errno.errno[err.errno].description;
  150. } else {
  151. description += "".concat(err.code, " ").concat(err.message);
  152. }
  153. console.error("lessc: failed to create file ".concat(filename));
  154. console.error(description);
  155. process.exitCode = 1;
  156. } else {
  157. less.logger.info("lessc: wrote ".concat(filename));
  158. }
  159. onDone();
  160. });
  161. };
  162. }
  163. var writeSourceMapIfNeeded = function writeSourceMapIfNeeded(output, onDone) {
  164. if (options.sourceMap && !sourceMapFileInline) {
  165. writeSourceMap(output, onDone);
  166. } else {
  167. onDone();
  168. }
  169. };
  170. var writeOutput = function writeOutput(output, result, onSuccess) {
  171. if (options.depends) {
  172. onSuccess();
  173. } else if (output) {
  174. ensureDirectory(output);
  175. fs.writeFile(output, result.css, {
  176. encoding: 'utf8'
  177. }, function (err) {
  178. if (err) {
  179. var description = 'Error: ';
  180. if (errno && errno.errno[err.errno]) {
  181. description += errno.errno[err.errno].description;
  182. } else {
  183. description += "".concat(err.code, " ").concat(err.message);
  184. }
  185. console.error("lessc: failed to create file ".concat(output));
  186. console.error(description);
  187. process.exitCode = 1;
  188. } else {
  189. less.logger.info("lessc: wrote ".concat(output));
  190. onSuccess();
  191. }
  192. });
  193. } else if (!options.depends) {
  194. process.stdout.write(result.css);
  195. onSuccess();
  196. }
  197. };
  198. var logDependencies = function logDependencies(options, result) {
  199. if (options.depends) {
  200. var depends = '';
  201. for (var i = 0; i < result.imports.length; i++) {
  202. depends += "".concat(result.imports[i], " ");
  203. }
  204. console.log(depends);
  205. }
  206. };
  207. var parseLessFile = function parseLessFile(e, data) {
  208. if (e) {
  209. console.error("lessc: ".concat(e.message));
  210. process.exitCode = 1;
  211. return;
  212. }
  213. data = data.replace(/^\uFEFF/, '');
  214. options.paths = [path.dirname(input)].concat(options.paths);
  215. options.filename = input;
  216. if (options.lint) {
  217. options.sourceMap = false;
  218. }
  219. sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
  220. if (options.sourceMap) {
  221. options.sourceMap = sourceMapOptions;
  222. }
  223. less.logger.addListener({
  224. info: function info(msg) {
  225. if (verbose) {
  226. console.log(msg);
  227. }
  228. },
  229. warn: function warn(msg) {
  230. // do not show warning if the silent option is used
  231. if (!silent) {
  232. console.warn(msg);
  233. }
  234. },
  235. error: function error(msg) {
  236. console.error(msg);
  237. }
  238. });
  239. less.render(data, options).then(function (result) {
  240. if (!options.lint) {
  241. writeOutput(output, result, function () {
  242. writeSourceMapIfNeeded(result.map, function () {
  243. logDependencies(options, result);
  244. });
  245. });
  246. }
  247. }, function (err) {
  248. if (!options.silent) {
  249. console.error(err.toString({
  250. stylize: options.color && less.lesscHelper.stylize
  251. }));
  252. }
  253. process.exitCode = 1;
  254. });
  255. };
  256. if (input != '-') {
  257. fs.readFile(input, 'utf8', parseLessFile);
  258. } else {
  259. process.stdin.resume();
  260. process.stdin.setEncoding('utf8');
  261. var buffer = '';
  262. process.stdin.on('data', function (data) {
  263. buffer += data;
  264. });
  265. process.stdin.on('end', function () {
  266. parseLessFile(false, buffer);
  267. });
  268. }
  269. }
  270. function processPluginQueue() {
  271. var x = 0;
  272. function pluginError(name) {
  273. console.error("Unable to load plugin ".concat(name, " please make sure that it is installed under or at the same level as less"));
  274. process.exitCode = 1;
  275. }
  276. function pluginFinished(plugin) {
  277. x++;
  278. plugins.push(plugin);
  279. if (x === queuePlugins.length) {
  280. render();
  281. }
  282. }
  283. queuePlugins.forEach(function (queue) {
  284. var context = utils.clone(options);
  285. pluginManager.Loader.loadPlugin(queue.name, process.cwd(), context, less.environment, fileManager).then(function (data) {
  286. pluginFinished({
  287. fileContent: data.contents,
  288. filename: data.filename,
  289. options: queue.options
  290. });
  291. }).catch(function () {
  292. pluginError(queue.name);
  293. });
  294. });
  295. } // self executing function so we can return
  296. (function () {
  297. args = args.filter(function (arg) {
  298. var match;
  299. match = arg.match(/^-I(.+)$/);
  300. if (match) {
  301. options.paths.push(match[1]);
  302. return false;
  303. }
  304. match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
  305. if (match) {
  306. arg = match[1];
  307. } else {
  308. return arg;
  309. }
  310. switch (arg) {
  311. case 'v':
  312. case 'version':
  313. console.log("lessc ".concat(less.version.join('.'), " (Less Compiler) [JavaScript]"));
  314. continueProcessing = false;
  315. break;
  316. case 'verbose':
  317. verbose = true;
  318. break;
  319. case 's':
  320. case 'silent':
  321. silent = true;
  322. break;
  323. case 'l':
  324. case 'lint':
  325. options.lint = true;
  326. break;
  327. case 'strict-imports':
  328. options.strictImports = true;
  329. break;
  330. case 'h':
  331. case 'help':
  332. printUsage();
  333. break;
  334. case 'x':
  335. case 'compress':
  336. options.compress = true;
  337. break;
  338. case 'insecure':
  339. options.insecure = true;
  340. break;
  341. case 'M':
  342. case 'depends':
  343. options.depends = true;
  344. break;
  345. case 'max-line-len':
  346. if (checkArgFunc(arg, match[2])) {
  347. options.maxLineLen = parseInt(match[2], 10);
  348. if (options.maxLineLen <= 0) {
  349. options.maxLineLen = -1;
  350. }
  351. }
  352. break;
  353. case 'no-color':
  354. options.color = false;
  355. break;
  356. case 'js':
  357. options.javascriptEnabled = true;
  358. break;
  359. case 'no-js':
  360. console.error('The "--no-js" argument is deprecated, as inline JavaScript ' + 'is disabled by default. Use "--js" to enable inline JavaScript (not recommended).');
  361. break;
  362. case 'include-path':
  363. if (checkArgFunc(arg, match[2])) {
  364. // ; supported on windows.
  365. // : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2
  366. options.paths = match[2].split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':').map(function (p) {
  367. if (p) {
  368. return path.resolve(process.cwd(), p);
  369. }
  370. });
  371. }
  372. break;
  373. case 'line-numbers':
  374. if (checkArgFunc(arg, match[2])) {
  375. options.dumpLineNumbers = match[2];
  376. }
  377. break;
  378. case 'source-map':
  379. options.sourceMap = true;
  380. if (match[2]) {
  381. sourceMapOptions.sourceMapFullFilename = match[2];
  382. }
  383. break;
  384. case 'source-map-rootpath':
  385. if (checkArgFunc(arg, match[2])) {
  386. sourceMapOptions.sourceMapRootpath = match[2];
  387. }
  388. break;
  389. case 'source-map-basepath':
  390. if (checkArgFunc(arg, match[2])) {
  391. sourceMapOptions.sourceMapBasepath = match[2];
  392. }
  393. break;
  394. case 'source-map-inline':
  395. case 'source-map-map-inline':
  396. sourceMapFileInline = true;
  397. options.sourceMap = true;
  398. break;
  399. case 'source-map-include-source':
  400. case 'source-map-less-inline':
  401. sourceMapOptions.outputSourceFiles = true;
  402. break;
  403. case 'source-map-url':
  404. if (checkArgFunc(arg, match[2])) {
  405. sourceMapOptions.sourceMapURL = match[2];
  406. }
  407. break;
  408. case 'source-map-no-annotation':
  409. sourceMapOptions.disableSourcemapAnnotation = true;
  410. break;
  411. case 'rp':
  412. case 'rootpath':
  413. if (checkArgFunc(arg, match[2])) {
  414. options.rootpath = match[2].replace(/\\/g, '/');
  415. }
  416. break;
  417. case 'ie-compat':
  418. console.warn('The --ie-compat option is deprecated, as it has no effect on compilation.');
  419. break;
  420. case 'relative-urls':
  421. console.warn('The --relative-urls option has been deprecated. Use --rewrite-urls=all.');
  422. options.rewriteUrls = Constants.RewriteUrls.ALL;
  423. break;
  424. case 'ru':
  425. case 'rewrite-urls':
  426. var m = match[2];
  427. if (m) {
  428. if (m === 'local') {
  429. options.rewriteUrls = Constants.RewriteUrls.LOCAL;
  430. } else if (m === 'off') {
  431. options.rewriteUrls = Constants.RewriteUrls.OFF;
  432. } else if (m === 'all') {
  433. options.rewriteUrls = Constants.RewriteUrls.ALL;
  434. } else {
  435. console.error("Unknown rewrite-urls argument ".concat(m));
  436. continueProcessing = false;
  437. process.exitCode = 1;
  438. }
  439. } else {
  440. options.rewriteUrls = Constants.RewriteUrls.ALL;
  441. }
  442. break;
  443. case 'sm':
  444. case 'strict-math':
  445. console.warn('The --strict-math option has been deprecated. Use --math=strict.');
  446. if (checkArgFunc(arg, match[2])) {
  447. if (checkBooleanArg(match[2])) {
  448. options.math = Constants.Math.PARENS;
  449. }
  450. }
  451. break;
  452. case 'm':
  453. case 'math':
  454. var m = match[2];
  455. if (checkArgFunc(arg, m)) {
  456. if (m === 'always') {
  457. console.warn('--math=always is deprecated and will be removed in the future.');
  458. options.math = Constants.Math.ALWAYS;
  459. } else if (m === 'parens-division') {
  460. options.math = Constants.Math.PARENS_DIVISION;
  461. } else if (m === 'parens' || m === 'strict') {
  462. options.math = Constants.Math.PARENS;
  463. } else if (m === 'strict-legacy') {
  464. console.warn('--math=strict-legacy has been removed. Defaulting to --math=strict');
  465. options.math = Constants.Math.PARENS;
  466. }
  467. }
  468. break;
  469. case 'su':
  470. case 'strict-units':
  471. if (checkArgFunc(arg, match[2])) {
  472. options.strictUnits = checkBooleanArg(match[2]);
  473. }
  474. break;
  475. case 'global-var':
  476. if (checkArgFunc(arg, match[2])) {
  477. if (!options.globalVars) {
  478. options.globalVars = {};
  479. }
  480. parseVariableOption(match[2], options.globalVars);
  481. }
  482. break;
  483. case 'modify-var':
  484. if (checkArgFunc(arg, match[2])) {
  485. if (!options.modifyVars) {
  486. options.modifyVars = {};
  487. }
  488. parseVariableOption(match[2], options.modifyVars);
  489. }
  490. break;
  491. case 'url-args':
  492. if (checkArgFunc(arg, match[2])) {
  493. options.urlArgs = match[2];
  494. }
  495. break;
  496. case 'plugin':
  497. var splitupArg = match[2].match(/^([^=]+)(=(.*))?/);
  498. var name = splitupArg[1];
  499. var pluginOptions = splitupArg[3];
  500. queuePlugins.push({
  501. name: name,
  502. options: pluginOptions
  503. });
  504. break;
  505. default:
  506. queuePlugins.push({
  507. name: arg,
  508. options: match[2],
  509. default: true
  510. });
  511. break;
  512. }
  513. });
  514. if (queuePlugins.length > 0) {
  515. processPluginQueue();
  516. } else {
  517. render();
  518. }
  519. })();