Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ module.exports = postcss.plugin('postcss-url', (options) => {
options = options || {};

return function(styles, result) {
const promises = [];
const opts = result.opts;
const from = opts.from ? path.dirname(opts.from) : '.';
const to = opts.to ? path.dirname(opts.to) : from;

styles.walkDecls((decl) =>
declProcessor(from, to, options, result, decl)
promises.push(declProcessor(from, to, options, result, decl))
);

return Promise.all(promises);
};
});

Expand Down
54 changes: 39 additions & 15 deletions src/lib/decl-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,42 @@ const getPattern = (decl) =>
* @param {Options} options
* @param {Result} result
* @param {Decl} decl
* @returns {String|undefined}
* @returns {Promise<String|undefined>}
*/
const replaceUrl = (url, dir, options, result, decl) => {
const asset = prepareAsset(url, dir, decl);

const matchedOptions = matchOptions(asset, options);

if (!matchedOptions) return;
if (!matchedOptions) return Promise.resolve();

const process = (option) => {
const wrappedUrlProcessor = wrapUrlProcessor(getUrlProcessor(option.url), result, decl);

return wrappedUrlProcessor(asset, dir, option);
};

let resultPromise = Promise.resolve();

if (Array.isArray(matchedOptions)) {
matchedOptions.forEach((option) => asset.url = process(option));
for (let i = 0; i < matchedOptions.length; i++) {
resultPromise = resultPromise
.then(() => process(matchedOptions[i]))
.then((newUrl) => {
asset.url = newUrl;

return newUrl;
});
}
} else {
asset.url = process(matchedOptions);
resultPromise = process(matchedOptions);
}

return asset.url;
return resultPromise.then((newUrl) => {
asset.url = newUrl;

return newUrl;
});
};

/**
Expand All @@ -110,31 +124,41 @@ const replaceUrl = (url, dir, options, result, decl) => {
* @param {PostcssUrl~Options} options
* @param {Result} result
* @param {Decl} decl
* @returns {PostcssUrl~DeclProcessor}
* @returns {Promise<PostcssUrl~DeclProcessor>}
*/
const declProcessor = (from, to, options, result, decl) => {
const dir = { from, to, file: getDirDeclFile(decl) };
const pattern = getPattern(decl);

if (!pattern) return;
if (!pattern) return Promise.resolve();

const promises = [];

decl.value = decl.value
.replace(pattern, (matched, before, url, after) => {
const newUrl = replaceUrl(url, dir, options, result, decl);
const newUrlPromise = replaceUrl(url, dir, options, result, decl);

promises.push(
newUrlPromise
.then((newUrl) => {
if (!newUrl) return matched;

if (!newUrl) return matched;
if (WITH_QUOTES.test(newUrl) && WITH_QUOTES.test(after)) {
before = before.slice(0, -1);
after = after.slice(1);
}

if (WITH_QUOTES.test(newUrl) && WITH_QUOTES.test(after)) {
before = before.slice(0, -1);
after = after.slice(1);
}
decl.value = decl.value.replace(matched, `${before}${newUrl}${after}`);
})
);

return `${before}${newUrl}${after}`;
return matched;
});

return Promise.all(promises);
};

module.exports = {
replaceUrl,
declProcessor
};

Expand Down
67 changes: 55 additions & 12 deletions src/lib/get-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,74 @@ const mime = require('mime');

const getPathByBasePath = require('./paths').getPathByBasePath;

const readFileAsync = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};

const existFileAsync = (filePath) => {
Comment thread
MrMeison marked this conversation as resolved.
return new Promise((resolve) =>
fs.access(filePath, (err) => {
resolve(!err);
})
);
};

const findExistsPath = (paths) => {
let resolved = false;

return new Promise((resolve, reject) => {
const findPromises = paths.map((path) => {
return existFileAsync(path).then((isExists) => {
if (!resolved && isExists) {
resolved = true;
resolve(path);
}
});
});

Promise.all(findPromises).then(() => {
if (!resolved) {
reject();
}
});
});
};

/**
*
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Options} options
* @param {PostcssUrl~Dir} dir
* @param {Function} warn
* @returns {PostcssUrl~File}
* @returns {Promise<PostcssUrl~File | Undefined>}
*/
const getFile = (asset, options, dir, warn) => {
const paths = options.basePath
? getPathByBasePath(options.basePath, dir.from, asset.pathname)
: [asset.absolutePath];
const filePath = paths.find(fs.existsSync);

if (!filePath) {
warn(`Can't read file '${paths.join()}', ignoring`);

return;
}
return findExistsPath(paths)
.then((path) => readFileAsync(path)
Comment thread
MrMeison marked this conversation as resolved.
.then((contents) => {
return {
path,
contents,
mimeType: mime.getType(path)
};
})
)
.catch(() => {
warn(`Can't read file '${paths.join()}', ignoring`);

return {
path: filePath,
contents: fs.readFileSync(filePath),
mimeType: mime.getType(filePath)
};
return;
});
};

module.exports = getFile;
Expand Down
70 changes: 46 additions & 24 deletions src/type/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,31 @@ const normalize = paths.normalize;

const getHashName = (file, options) =>
(options && options.append ? (`${path.basename(file.path, path.extname(file.path))}_`) : '')
+ calcHash(file.contents, options)
+ path.extname(file.path);
+ calcHash(file.contents, options)
+ path.extname(file.path);

const createDirAsync = (dirPath) => {
return new Promise((resolve, reject) => {
mkdirp(dirPath, (err) => {
if (err) {
reject(err);
}

resolve();
});
});
};

const writeFileAsync = (file, dest) => {
return new Promise((resolve, reject) => {
fs.writeFile(dest, file.contents, { flag: 'wx' }, (err) => {
if (err) {
err.code === 'EEXIST' ? resolve() : reject(err);
}
resolve();
});
});
};

/**
* Copy images from readed from url() to an specific assets destination
Expand All @@ -33,37 +56,36 @@ const getHashName = (file, options) =>
* @param {Result} result
* @param {Function} addDependency
*
* @returns {String|Undefined}
* @returns {Promise<String|Undefined>}
*/

module.exports = function processCopy(asset, dir, options, decl, warn, result, addDependency) {
if (!options.assetsPath && dir.from === dir.to) {
warn('Option `to` of postcss is required, ignoring');

return;
return Promise.resolve();
}

const file = getFile(asset, options, dir, warn);

if (!file) return;

const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;
return getFile(asset, options, dir, warn)
.then((file) => {
if (!file) return;

const targetDir = getTargetDir(dir);
const newAssetBaseDir = getAssetsPath(targetDir, options.assetsPath);
const newAssetPath = path.join(newAssetBaseDir, assetRelativePath);
const newRelativeAssetPath = normalize(
path.relative(targetDir, newAssetPath)
);
const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;

mkdirp.sync(path.dirname(newAssetPath));

if (!fs.existsSync(newAssetPath)) {
fs.writeFileSync(newAssetPath, file.contents);
}
const targetDir = getTargetDir(dir);
const newAssetBaseDir = getAssetsPath(targetDir, options.assetsPath);
const newAssetPath = path.join(newAssetBaseDir, assetRelativePath);
const newRelativeAssetPath = normalize(path.relative(targetDir, newAssetPath));

addDependency(file.path);
return createDirAsync(path.dirname(newAssetPath))
.then(() => writeFileAsync(file, newAssetPath))
.then(() => {
addDependency(file.path);

return `${newRelativeAssetPath}${asset.search}${asset.hash}`;
return `${newRelativeAssetPath}${asset.search}${asset.hash}`;
});
}
);
};
4 changes: 2 additions & 2 deletions src/type/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Option} options
*
* @returns {String|Undefined}
* @returns {Promise<String|Undefined>}
*/
module.exports = function getCustomProcessor(asset, dir, options) {
return options.url.apply(null, arguments);
return Promise.resolve().then(() => options.url.apply(null, arguments));
};
Loading