Files
meteor/packages/less/plugin/compile-less.js
David Glasser 4e7b24dcb3 also catch sync errors thrown by less.render
(which wouldn't actually have caught the issue in 1e3b09d, because that
error got caught by some internal less try/catch, sigh)
2015-07-24 14:51:50 -07:00

166 lines
4.7 KiB
JavaScript

const path = Plugin.path;
const less = Npm.require('less');
const Future = Npm.require('fibers/future');
Plugin.registerCompiler({
// *.lessimport has been deprecated since 0.7.1, but it still works. We
// *recommend *.import.less or the imports subdirectory instead.
extensions: ['less', 'lessimport'],
archMatching: 'web'
}, () => new LessCompiler());
// CompileResult is {css, sourceMap}.
class LessCompiler extends MultiFileCachingCompiler {
constructor() {
super({
compilerName: 'less',
defaultCacheSize: 1024*1024*10,
});
}
getCacheKey(inputFile) {
return inputFile.getSourceHash();
}
compileResultSize(compileResult) {
return compileResult.css.length +
this.sourceMapSize(compileResult.sourceMap);
}
// The heuristic is that a file is an import (ie, is not itself processed as a
// root) if it is in a subdirectory named 'imports' or if it matches
// *.import.less. This can be overridden in either direction via an explicit
// `isImport` file option in api.addFiles.
isRoot(inputFile) {
const fileOptions = inputFile.getFileOptions();
if (fileOptions.hasOwnProperty('isImport')) {
return !fileOptions.isImport;
}
const pathInPackage = inputFile.getPathInPackage();
return !(/\.import\.less$/.test(pathInPackage) ||
/\.lessimport$/.test(pathInPackage) ||
/(?:^|\/)imports\//.test(pathInPackage));
}
compileOneFile(inputFile, allFiles) {
const importPlugin = new MeteorImportLessPlugin(allFiles);
const f = new Future;
let output;
try {
less.render(inputFile.getContentsAsBuffer().toString('utf8'), {
filename: this.getAbsoluteImportPath(inputFile),
plugins: [importPlugin],
// Generate a source map, and include the source files in the
// sourcesContent field. (Note that source files which don't themselves
// produce text (eg, are entirely variable definitions) won't end up in
// the source map!)
sourceMap: { outputSourceFiles: true }
}, f.resolver());
output = f.wait();
} catch (e) {
inputFile.error({
message: e.message,
sourcePath: decodeFilePath(e.filename),
line: e.line,
column: e.column
});
return null;
}
if (output.map) {
const map = JSON.parse(output.map);
map.sources = map.sources.map(decodeFilePath);
output.map = map;
}
const compileResult = {css: output.css, sourceMap: output.map};
const referencedImportPaths = [];
output.imports.forEach((path) => {
if (! allFiles.has(path)) {
throw Error("Imported an unknown file?");
}
referencedImportPaths.push(path);
});
return {compileResult, referencedImportPaths};
}
addCompileResult(inputFile, compileResult) {
inputFile.addStylesheet({
data: compileResult.css,
path: inputFile.getPathInPackage() + '.css',
sourceMap: compileResult.sourceMap
});
}
}
class MeteorImportLessPlugin {
constructor(allFiles) {
this.allFiles = allFiles;
this.minVersion = [2, 5, 0];
}
install(less, pluginManager) {
pluginManager.addFileManager(
new MeteorImportLessFileManager(this.allFiles));
}
}
class MeteorImportLessFileManager extends less.AbstractFileManager {
constructor(allFiles) {
super();
this.allFiles = allFiles;
}
// We want to be the only active FileManager, so claim to support everything.
supports() {
return true;
}
loadFile(filename, currentDirectory, options, environment, cb) {
const packageMatch = currentDirectory.match(/^(\{[^}]*\})/);
if (! packageMatch) {
// shouldn't happen. all filenames less ever sees should involve this {}
// thing!
cb(new Error('file without Meteor context? ' + currentDirectory));
return;
}
const currentPackagePrefix = packageMatch[1];
let resolvedFilename;
if (filename[0] === '/') {
// Map `/foo/bar.less` onto `{thispackage}/foo/bar.less`
resolvedFilename = currentPackagePrefix + filename;
} else if (filename[0] === '{') {
resolvedFilename = filename;
} else {
resolvedFilename = path.join(currentDirectory, filename);
}
if (!this.allFiles.has(resolvedFilename)) {
cb({type: 'File', message: 'Unknown import: ' + filename});
return;
}
cb(null, {
contents: this.allFiles.get(resolvedFilename)
.getContentsAsBuffer().toString('utf8'),
filename: resolvedFilename
});
}
}
function decodeFilePath (filePath) {
const match = filePath.match(/^{(.*)}\/(.*)$/);
if (! match)
throw new Error('Failed to decode Less path: ' + filePath);
if (match[1] === '') {
// app
return match[2];
}
return 'packages/' + match[1] + '/' + match[2];
}