Skip to content

Instantly share code, notes, and snippets.

@vitalyrotari
Created January 26, 2016 08:56
Show Gist options
  • Select an option

  • Save vitalyrotari/36db5ab13aa3351b4417 to your computer and use it in GitHub Desktop.

Select an option

Save vitalyrotari/36db5ab13aa3351b4417 to your computer and use it in GitHub Desktop.
METEOR@1.3-modules-beta.4 SCSS fixes
const path = Plugin.path;
const fs = Plugin.fs;
const sass = Npm.require('node-sass');
const Future = Npm.require('fibers/future');
const files = Plugin.files;
Plugin.registerCompiler({
extensions: ['scss', 'sass'],
archMatching: 'web'
}, () => new SassCompiler());
function hasUnderscore(file) {
return path.basename(file)[0] === '_';
}
// CompileResult is {css, sourceMap}.
class SassCompiler extends MultiFileCachingCompiler {
constructor() {
super({
compilerName: 'sass',
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 matches _*.sass, _*.scss
// 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();
const fileBaseName = inputFile.getBasename();
return !(/^_/.test(fileBaseName) ||
/\.import\.(sass|scss)$/.test(pathInPackage) ||
/\.(sass|scss)import$/.test(pathInPackage) ||
/(?:^|\/)imports\//.test(pathInPackage));
}
compileOneFile(inputFile, allFiles) {
const referencedImportPaths = [];
const totalImportPath = [];
const sourceMapPaths = ['.' + inputFile.getDisplayPath()];
//Handle deprecation of fs.existsSYnc
//XXX: remove when meteor is fully on node 4+
function fileExists(file) {
if (fs.accessSync) {
try {
fs.accessSync(file, fs.R_OK);
} catch (e) {
return false;
}
return true;
} else {
return fs.existsSync(file);
}
}
function addUnderscore(file) {
if (!hasUnderscore(file)) {
file = path.join(path.dirname(file), '_' + path.basename(file));
}
return file;
}
const getRealImportPath = function (importPath) {
const rawImportPath = importPath;
var isAbsolute = false;
if (importPath[0] === '/') {
importPath = path.join(process.cwd(), importPath);
isAbsolute = true;
}
//SASS has a whole range of possible import files from one import statement, try each of them
const possibleFiles = [];
//If the referenced file has no extension, try possible extensions, starting with extension of the parent file.
let possibleExtensions = ['scss', 'sass', 'css'];
if (!importPath.match(/\.s?(a|c)ss$/)) {
possibleExtensions = [inputFile.getExtension()].concat(_.without(possibleExtensions, inputFile.getExtension()));
for (const extension of possibleExtensions) {
possibleFiles.push(importPath + '.' + extension);
}
} else {
possibleFiles.push(importPath);
}
//Try files prefixed with underscore
for (let possibleFile of possibleFiles) {
if (!hasUnderscore(possibleFile)) {
possibleFiles.push(addUnderscore(possibleFile));
}
}
//Try if one of the possible files exists
for (let possibleFile of possibleFiles) {
if ((isAbsolute && fileExists(possibleFile)) || (!isAbsolute && allFiles.has(possibleFile))) {
return {absolute: isAbsolute, path: possibleFile};
}
}
//Nothing found...
throw new Error(`File to import: ${rawImportPath} not found in file: ${totalImportPath[totalImportPath.length - 2]}`);
};
//Handle import statements found by the sass compiler, used to handle cross-package imports
const importer = function (url, prev, done) {
if (!totalImportPath.length) {
totalImportPath.push(prev);
}
if (totalImportPath[totalImportPath.length] !== prev) {
//backtracked, splice of part we don't need anymore
// (XXX: this might give problems when multiple parts of the path have the same name)
totalImportPath.splice(totalImportPath.indexOf(prev) + 1, totalImportPath.length);
}
let importPath = url;
for (let i = totalImportPath.length - 1; i >= 0; i--) {
if (importPath[0] === '/' || importPath[0] === '{') {
break;
}
importPath = path.join(path.dirname(totalImportPath[i]), importPath);
}
totalImportPath.push(url);
let accPosition = importPath.indexOf('{');
if (accPosition > -1) {
importPath = importPath.substr(accPosition, importPath.length);
}
try {
const parsed = getRealImportPath(importPath);
if (parsed.absolute) {
sourceMapPaths.push(parsed.path);
done({contents: fs.readFileSync(parsed.path, 'utf8')});
} else {
referencedImportPaths.push(parsed.path);
sourceMapPaths.push(decodeFilePath(parsed.path));
done({contents: allFiles.get(parsed.path).getContentsAsString()});
}
} catch (e) {
return done(e);
}
};
const f = new Future;
const options = {
sourceMap: true,
sourceMapContents: true,
sourceMapEmbed: false,
sourceComments: false,
sourceMapRoot: '.',
indentedSyntax : inputFile.getExtension() === 'sass',
outFile: '.'+inputFile.getBasename(),
importer: importer,
includePaths: []
};
options.file = this.getAbsoluteImportPath(inputFile);
options.data = inputFile.getContentsAsBuffer().toString('utf8');
//If the file is empty, options.data is an empty string
// In that case options.file will be used by node-sass,
// which it can not read since it will contain a meteor package or app reference '{}'
// This is one workaround, another one would be to not set options.file, in which case the importer 'prev' will be 'stdin'
// However, this would result in problems if a file named std�n.scss would exist.
// Not the most elegant of solutions, but it works.
if (!options.data.trim()) {
options.data = "$fakevariable : blue;"
}
let output;
try {
sass.render(options, f.resolver());
output = f.wait();
} catch (e) {
inputFile.error({
message: `[SCSS Compiler Error] ${e.message}\n`,
sourcePath: inputFile.getDisplayPath(),
line: e.line,
column: e.column
});
return null;
}
if (output.map) {
const map = JSON.parse(output.map.toString('utf-8'));
map.sources = sourceMapPaths;
output.map = map;
}
const compileResult = {css: output.css.toString('utf-8'), sourceMap: output.map};
return {compileResult, referencedImportPaths};
}
addCompileResult(inputFile, compileResult) {
inputFile.addStylesheet({
data: compileResult.css,
path: inputFile.getPathInPackage() + '.css',
sourceMap: compileResult.sourceMap
});
}
}
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];
}
@infacq

infacq commented Mar 23, 2016

Copy link
Copy Markdown

how to use it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment