125 lines
6.3 KiB
JavaScript
125 lines
6.3 KiB
JavaScript
import LessError from './less-error.js';
|
|
import transformTree from './transform-tree.js';
|
|
import logger from './logger.js';
|
|
|
|
export default function(SourceMapBuilder) {
|
|
class ParseTree {
|
|
constructor(root, imports) {
|
|
this.root = root;
|
|
this.imports = imports;
|
|
}
|
|
|
|
toCSS(options) {
|
|
let evaldRoot;
|
|
const result = {};
|
|
let sourceMapBuilder;
|
|
try {
|
|
evaldRoot = transformTree(this.root, options);
|
|
} catch (e) {
|
|
throw new LessError(e, this.imports);
|
|
}
|
|
|
|
try {
|
|
const compress = Boolean(options.compress);
|
|
if (compress) {
|
|
logger.warn('The compress option has been deprecated. ' +
|
|
'We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.');
|
|
}
|
|
|
|
const toCSSOptions = {
|
|
compress,
|
|
// @deprecated The dumpLineNumbers option is deprecated. Use sourcemaps instead. All modes will be removed in a future version.
|
|
dumpLineNumbers: options.dumpLineNumbers,
|
|
strictUnits: Boolean(options.strictUnits),
|
|
numPrecision: 8};
|
|
|
|
if (options.sourceMap) {
|
|
// Normalize sourceMap option: if it's just true, convert to object
|
|
if (options.sourceMap === true) {
|
|
options.sourceMap = {};
|
|
}
|
|
const sourceMapOpts = options.sourceMap;
|
|
|
|
// Set sourceMapInputFilename if not set and filename is available
|
|
if (!sourceMapOpts.sourceMapInputFilename && options.filename) {
|
|
sourceMapOpts.sourceMapInputFilename = options.filename;
|
|
}
|
|
|
|
// Default sourceMapBasepath to the input file's directory if not set
|
|
// This matches the behavior documented and implemented in bin/lessc
|
|
if (sourceMapOpts.sourceMapBasepath === undefined && options.filename) {
|
|
// Get directory from filename using string manipulation (works cross-platform)
|
|
const lastSlash = Math.max(options.filename.lastIndexOf('/'), options.filename.lastIndexOf('\\'));
|
|
if (lastSlash >= 0) {
|
|
sourceMapOpts.sourceMapBasepath = options.filename.substring(0, lastSlash);
|
|
} else {
|
|
// No directory separator found, use current directory
|
|
sourceMapOpts.sourceMapBasepath = '.';
|
|
}
|
|
}
|
|
|
|
// Handle sourceMapFullFilename (CLI-specific: --source-map=filename)
|
|
// This is converted to sourceMapFilename and sourceMapOutputFilename
|
|
if (sourceMapOpts.sourceMapFullFilename && !sourceMapOpts.sourceMapFileInline) {
|
|
// This case is handled by lessc before calling render
|
|
// We just need to ensure sourceMapFilename is set if sourceMapFullFilename is provided
|
|
if (!sourceMapOpts.sourceMapFilename && !sourceMapOpts.sourceMapURL) {
|
|
// Extract just the basename for the sourceMappingURL comment
|
|
const mapBase = sourceMapOpts.sourceMapFullFilename.split(/[/\\]/).pop();
|
|
sourceMapOpts.sourceMapFilename = mapBase;
|
|
}
|
|
} else if (!sourceMapOpts.sourceMapFilename && !sourceMapOpts.sourceMapURL) {
|
|
// If sourceMapFilename is not set and sourceMapURL is not set,
|
|
// derive it from the output filename (if available) or input filename
|
|
if (sourceMapOpts.sourceMapOutputFilename) {
|
|
// Use output filename + .map
|
|
sourceMapOpts.sourceMapFilename = sourceMapOpts.sourceMapOutputFilename + '.map';
|
|
} else if (options.filename) {
|
|
// Fallback to input filename + .css.map (basename only)
|
|
const inputBasename = options.filename.split(/[/\\]/).pop().replace(/\.[^/.]+$/, '');
|
|
sourceMapOpts.sourceMapFilename = inputBasename + '.css.map';
|
|
}
|
|
}
|
|
|
|
// Default sourceMapOutputFilename if not set
|
|
if (!sourceMapOpts.sourceMapOutputFilename) {
|
|
if (options.filename) {
|
|
const inputBasename = options.filename.split(/[/\\]/).pop().replace(/\.[^/.]+$/, '');
|
|
sourceMapOpts.sourceMapOutputFilename = inputBasename + '.css';
|
|
} else {
|
|
sourceMapOpts.sourceMapOutputFilename = 'output.css';
|
|
}
|
|
}
|
|
|
|
sourceMapBuilder = new SourceMapBuilder(sourceMapOpts);
|
|
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
|
|
} else {
|
|
result.css = evaldRoot.toCSS(toCSSOptions);
|
|
}
|
|
} catch (e) {
|
|
throw new LessError(e, this.imports);
|
|
}
|
|
|
|
if (options.pluginManager) {
|
|
const postProcessors = options.pluginManager.getPostProcessors();
|
|
for (let i = 0; i < postProcessors.length; i++) {
|
|
result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options, imports: this.imports });
|
|
}
|
|
}
|
|
if (options.sourceMap) {
|
|
result.map = sourceMapBuilder.getExternalSourceMap();
|
|
}
|
|
|
|
result.imports = [];
|
|
for (const file in this.imports.files) {
|
|
if (Object.prototype.hasOwnProperty.call(this.imports.files, file) && file !== this.imports.rootFilename) {
|
|
result.imports.push(file);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return ParseTree;
|
|
}
|