-
Notifications
You must be signed in to change notification settings - Fork 50
Add support for Sass includePaths option
#55
Changes from 4 commits
89f376b
0af1687
6bc4dbe
ee81783
2e29769
8902b1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import path from 'path'; | ||
| import fs from 'fs'; | ||
| import toutSuite from 'toutsuite'; | ||
|
|
||
| import {CompilerBase} from '../compiler-base'; | ||
|
|
@@ -48,15 +49,21 @@ export default class SassCompiler extends CompilerBase { | |
|
|
||
| paths.unshift('.'); | ||
|
|
||
| const { includePaths } = this.compilerOptions | ||
| if (includePaths) { | ||
| sass.importer(this.buildImporterCallback(includePaths)) | ||
| delete this.compilerOptions.includePaths; | ||
| } | ||
|
|
||
| let opts = Object.assign({}, this.compilerOptions, { | ||
| indentedSyntax: filePath.match(/\.sass$/i), | ||
| sourceMapRoot: filePath, | ||
| sourceMapRoot: filePath, | ||
| }); | ||
|
|
||
| let result = await new Promise((res,rej) => { | ||
| sass.compile(sourceCode, opts, (r) => { | ||
| if (r.status !== 0) { | ||
| rej(new Error(r.formatted)); | ||
| rej(new Error(r.formatted || r.message)); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -93,9 +100,15 @@ export default class SassCompiler extends CompilerBase { | |
|
|
||
| paths.unshift('.'); | ||
|
|
||
| const { includePaths } = this.compilerOptions | ||
| if (includePaths) { | ||
| sass.importer(this.buildImporterCallback(includePaths)) | ||
| delete this.compilerOptions.includePaths; | ||
| } | ||
|
|
||
| let opts = Object.assign({}, this.compilerOptions, { | ||
| indentedSyntax: filePath.match(/\.sass$/i), | ||
| sourceMapRoot: filePath, | ||
| sourceMapRoot: filePath, | ||
| }); | ||
|
|
||
| let result; | ||
|
|
@@ -114,8 +127,67 @@ export default class SassCompiler extends CompilerBase { | |
| }; | ||
| } | ||
|
|
||
| buildImporterCallback (includePaths) { | ||
| const self = this; | ||
| return (function (request, done) { | ||
| let file | ||
| if (request.file) { | ||
| done(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing return statement
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
| } else { | ||
| // sass.js works in the '/sass/' directory | ||
| const cleanedRequestPath = request.resolved.replace(/^\/sass\//, ''); | ||
| for (let includePath of includePaths) { | ||
| const filePath = path.resolve(includePath, cleanedRequestPath); | ||
| let variations = sass.getPathVariations(filePath); | ||
|
|
||
| file = variations | ||
| .map(self.fixWindowsPath.bind(self)) | ||
| .reduce(self.importedFileReducer.bind(self), null); | ||
|
|
||
| if (file) { | ||
| const content = fs.readFileSync(file, { encoding: 'utf8' }); | ||
| return sass.writeFile(file, content, () => { | ||
| done({ path: file }) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return here too - even though we don't need it, just get in the habit of always writing return after a callback, it will save you much sanity in the long run
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (!file) done(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| importedFileReducer(found, path) { | ||
| // Find the first variation that actually exists | ||
| if (found) return found; | ||
|
|
||
| try { | ||
| const stat = fs.statSync(path); | ||
| if (!stat.isFile()) return null; | ||
| return path; | ||
| } catch(e) { | ||
| return null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably want to debug log this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about this - sass.js returns ~25 variations, each invalid one will fail here and trigger a debug log. In medium-sized projects with multiple sass files, this will generate a whole lot of unnecessary output. Correct me if I'm wrong :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @saschagehlich Use the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know about the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, in that case we'll just hope that Sass provides enough debugging
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If sass.js doesn't find an imported file, it reports an error anyway, so I guess that's fine |
||
| } | ||
| } | ||
|
|
||
| fixWindowsPath(file) { | ||
| // Unfortunately, there's a bug in sass.js that seems to ignore the different | ||
| // path separators across platforms. We need to fix this. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give some examples of what gets passed in and what gets returned?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ (see new commits) |
||
| if (process.platform === 'win32' && file[0] === '/') { | ||
| file = file.slice(1); | ||
| } | ||
|
|
||
| if (file[0] === '_') { | ||
| const parts = file.slice(1).split(path.sep); | ||
| const dir = parts.slice(0, -1).join(path.sep); | ||
| const fileName = parts.reverse()[0]; | ||
| file = path.resolve(dir, '_' + fileName); | ||
| } | ||
| return file | ||
| } | ||
|
|
||
| getCompilerVersion() { | ||
| // NB: There is a bizarre bug in the node module system where this doesn't | ||
| // NB: There is a bizarre bug in the node module system where this doesn't | ||
| // work but only in saveConfiguration tests | ||
| //return require('@paulcbetts/node-sass/package.json').version; | ||
| return "4.1.1"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| body, html | ||
| background: red |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import path from 'path' | ||
| import SassCompiler from '../../src/css/sass'; | ||
|
|
||
| describe('css/sass', function () { | ||
| this.timeout(5000) | ||
| it('should correctly compile valid sass', (done) => { | ||
| const sass = 'body, html\n background: red'; | ||
| const filename = 'file.sass'; | ||
|
|
||
| const compiler = new SassCompiler(); | ||
| compiler.compilerOptions = { comments: false }; | ||
|
|
||
| compiler.compile(sass, filename) | ||
| .then((result) => { | ||
| expect(result.mimeType).to.equal('text/css'); | ||
| expect(result.code).to.equal('body, html {\n background: red; }\n'); | ||
| done(); | ||
| }) | ||
| .catch((err) => { | ||
| done(err); | ||
| }); | ||
| }); | ||
|
|
||
| describe('when passing includePaths', () => { | ||
| it('should correctly resolve import paths', (done) => { | ||
| const sass = '@import "importable"'; | ||
| const filename = 'file.sass'; | ||
|
|
||
| const compiler = new SassCompiler(); | ||
| compiler.compilerOptions = Object.assign({}, compiler.compilerOptions, { | ||
| includePaths: ['test/css/fixtures'], | ||
| comments: false | ||
| }); | ||
|
|
||
| compiler.compile(sass, filename) | ||
| .then((result) => { | ||
| expect(result.mimeType).to.equal('text/css'); | ||
| expect(result.code).to.equal('body, html {\n background: red; }\n'); | ||
| done(); | ||
| }) | ||
| .catch((err) => { | ||
| done(err); | ||
| }); | ||
| }) | ||
| }) | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --require babel-register | ||
| --require test/support.js | ||
| --reporter spec | ||
| --recursive |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import chai from 'chai' | ||
| import chaiAsPromised from 'chai-as-promised' | ||
|
|
||
| global.expect = chai.expect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good fix!