Skip to content

Commit

Permalink
feat(importer): add support for custom importers
Browse files Browse the repository at this point in the history
  • Loading branch information
jgerigmeyer committed Jan 12, 2018
1 parent 88ea499 commit fc919a0
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function extract(rendered, { compileOptions = {}, extractOptions = {} } =
.then(({ compiledFiles, orderedFiles }) => {
const parsedDeclarations = parseFiles(compiledFiles);
const extractions = processFiles(orderedFiles, compiledFiles, parsedDeclarations, pluggable);
const importer = makeImporter(extractions, includedFiles, includedPaths);
const importer = makeImporter(extractions, includedFiles, includedPaths, compileOptions.importer);
const extractionCompileOptions = makeExtractionCompileOptions(compileOptions, entryFilename, extractions, importer);

return sass.renderAsync(extractionCompileOptions)
Expand All @@ -117,7 +117,7 @@ export function extractSync(rendered, { compileOptions = {}, extractOptions = {}
const { compiledFiles, orderedFiles } = loadCompiledFilesSync(includedFiles, entryFilename, compileOptions.data);
const parsedDeclarations = parseFiles(compiledFiles);
const extractions = processFiles(orderedFiles, compiledFiles, parsedDeclarations, pluggable);
const importer = makeSyncImporter(extractions, includedFiles, includedPaths);
const importer = makeSyncImporter(extractions, includedFiles, includedPaths, compileOptions.importer);
const extractionCompileOptions = makeExtractionCompileOptions(compileOptions, entryFilename, extractions, importer);

sass.renderSync(extractionCompileOptions);
Expand Down
70 changes: 63 additions & 7 deletions src/importer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Promise from 'bluebird';
import path from 'path';
import { normalizePath, makeAbsolute } from './util';

Expand All @@ -15,9 +16,14 @@ function findImportedPath(url, prev, includedFilesMap, includedPaths) {
}

for(let i = 0; i < candidateFromPaths.length; i++) {
// Get normalize absolute candidate from path
let candidatePath;
let candidateFromPath = normalizePath(makeAbsolute(candidateFromPaths[i]));
let candidatePath = path.posix.join(candidateFromPath, url);
if (path.isAbsolute(url)) {
candidatePath = normalizePath(url);
} else {
// Get normalize absolute candidate from path
candidatePath = path.posix.join(candidateFromPath, url);
}

if(includedFilesMap[candidatePath]) {
return candidatePath;
Expand All @@ -26,7 +32,12 @@ function findImportedPath(url, prev, includedFilesMap, includedPaths) {
let indexOfBasename = url.lastIndexOf(urlBasename);
let partialUrl = `${url.substring(0, indexOfBasename)}_${urlBasename}`;

candidatePath = path.posix.join(candidateFromPath, partialUrl);
if (path.isAbsolute(partialUrl)) {
candidatePath = normalizePath(partialUrl);
} else {
candidatePath = path.posix.join(candidateFromPath, partialUrl);
}

if(includedFilesMap[candidatePath]) {
return candidatePath;
}
Expand Down Expand Up @@ -76,13 +87,45 @@ function getIncludedFilesMap(includedFiles) {
* Create an importer that will resolve @import directives with the injected
* data found in provided extractions object
*/
export function makeImporter(extractions, includedFiles, includedPaths) {
export function makeImporter(extractions, includedFiles, includedPaths, customImporter) {
const includedFilesMap = getIncludedFilesMap(includedFiles);

return function(url, prev, done) {
try {
const result = getImportResult(extractions, url, prev, includedFilesMap, includedPaths);
done(result);
let promise = Promise.resolve();
if (customImporter) {
promise = new Promise(resolve => {
if (Array.isArray(customImporter)) {
const promises = [];
customImporter.forEach(importer => {
const thisPromise = new Promise(res => {
const modifiedUrl = importer(url, prev, res);
if (modifiedUrl !== undefined) {
res(modifiedUrl);
}
});
promises.push(thisPromise);
})
Promise.all(promises).then(results => {
resolve(results.find(item => item !== null));
});
} else {
const modifiedUrl = customImporter(url, prev, resolve);
if (modifiedUrl !== undefined) {
resolve(modifiedUrl);
}
}
});
}
promise.then(modifiedUrl => {
if (modifiedUrl && modifiedUrl.file) {
url = modifiedUrl.file;
}
const result = getImportResult(extractions, url, prev, includedFilesMap, includedPaths);
done(result);
}).catch(err => {
done(err);
});
} catch(err) {
done(err);
}
Expand All @@ -93,11 +136,24 @@ export function makeImporter(extractions, includedFiles, includedPaths) {
* Create a synchronous importer that will resolve @import directives with the injected
* data found in provided extractions object
*/
export function makeSyncImporter(extractions, includedFiles, includedPaths) {
export function makeSyncImporter(extractions, includedFiles, includedPaths, customImporter) {
const includedFilesMap = getIncludedFilesMap(includedFiles);

return function(url, prev) {
try {
if (customImporter) {
let modifiedUrl;
if (Array.isArray(customImporter)) {
customImporter.forEach(importer => {
modifiedUrl = modifiedUrl || importer(url, prev);
})
} else {
modifiedUrl = customImporter(url, prev);
}
if (modifiedUrl && modifiedUrl.file) {
url = modifiedUrl.file;
}
}
const result = getImportResult(extractions, url, prev, includedFilesMap, includedPaths);
return result;
} catch(err) {
Expand Down
61 changes: 61 additions & 0 deletions test/include.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { normalizePath } = require('../src/util');
const includeRootFile = path.join(__dirname, 'sass', 'include', 'root.scss');
const includeRoot2File = path.join(__dirname, 'sass', 'include', 'root2.scss');
const includeRoot3File = path.join(__dirname, 'sass', 'include', 'root3.scss');
const includeRoot4File = path.join(__dirname, 'sass', 'include', 'root4.scss');
const includeSubFile = path.join(__dirname, 'sass', 'include', 'sub', 'included.scss');
const includeSubFile2 = path.join(__dirname, 'sass', 'include', 'sub', 'included2.scss');
const includeSubDir = path.join(__dirname, 'sass', 'include', 'sub');
Expand Down Expand Up @@ -266,4 +267,64 @@ describe('include', () => {
});
});
});

describe('custom importer', () => {
const getNewUrl = url => url === 'foo' ? './included.scss' : url;

describe('sync', () => {
it('should extract all variables', () => {
const rendered = renderSync({ file: includeRoot4File, includePaths: [includeSubDir], importer: url => ({ file: getNewUrl(url) }) });
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});

describe('async', () => {
it('should extract all variables', () => {
return render({ file: includeRoot4File, includePaths: [includeSubDir], importer: (url, prev, done) => { done({ file: getNewUrl(url) }); } })
.then(rendered => {
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});
});
});

describe('array of custom importers', () => {
const getNewUrl = url => url === 'foo' ? './included.scss' : url;

describe('sync', () => {
it('should extract all variables', () => {
const rendered = renderSync({ file: includeRoot4File, includePaths: [includeSubDir], importer: [() => null, url => ({ file: getNewUrl(url) })] });
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});

describe('async', () => {
it('should extract all variables', () => {
return render({ file: includeRoot4File, includePaths: [includeSubDir], importer: [(url, prev, done) => { done(null); }, (url, prev, done) => { done({ file: getNewUrl(url) }); }] })
.then(rendered => {
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});
});
});

describe('absolute include path', () => {
const getNewUrl = url => url === 'foo' ? path.join(__dirname, 'sass', 'include', 'sub', 'included.scss') : url;

describe('sync', () => {
it('should extract all variables', () => {
const rendered = renderSync({ file: includeRoot4File, importer: url => ({ file: getNewUrl(url) }) });
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});

describe('async', () => {
it('should extract all variables', () => {
return render({ file: includeRoot4File, importer: (url, prev, done) => { done({ file: getNewUrl(url) }); } })
.then(rendered => {
verifyFunctions(rendered, includeRoot4File, SUB_INCLUDED_COLOR, SUB_INCLUDED2_COLOR, includeSubFile, includeSubFile2);
});
});
});
});
});
2 changes: 2 additions & 0 deletions test/sass/include/root4.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@import 'foo';
$color: $includedColor;

0 comments on commit fc919a0

Please sign in to comment.