lessc 17 KB

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