Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise for everything #134

Merged
merged 4 commits into from Apr 16, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/index.js
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
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
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) => {
MrMeison marked this conversation as resolved.
Show resolved Hide 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)
MrMeison marked this conversation as resolved.
Show resolved Hide 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
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
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));
};