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 3 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
58 changes: 46 additions & 12 deletions src/lib/get-file.js
Expand Up @@ -5,31 +5,65 @@ 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, reject) =>
fs.access(filePath, (err) => {
if (err) {
reject();
}
resolve(filePath);
})
);
};

const oneSuccess = (promises) => {
return Promise.all(promises.map((p) => {
return p.then(
(val) => Promise.reject(val),
(err) => Promise.resolve(err)
);
})).then(
(errors) => Promise.reject(errors),
(val) => Promise.resolve(val)
);
};

/**
*
* @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 oneSuccess(paths.map((path) => existFileAsync(path)))
MrMeison marked this conversation as resolved.
Show resolved Hide resolved
.then((path) => readFileAsync(path)
MrMeison marked this conversation as resolved.
Show resolved Hide resolved
.then((contents) => ({
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
87 changes: 63 additions & 24 deletions src/type/copy.js
Expand Up @@ -14,8 +14,48 @@ 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.open(dest, 'wx', (err, fd) => {
MrMeison marked this conversation as resolved.
Show resolved Hide resolved
if (err) {
if (err.code === 'EEXIST') {
resolve();
}

reject(err);
}

resolve(fd);
});
})
.then((fd) => {
if (!fd) return;

return new Promise((resolve, reject) => {
fs.writeFile(dest, file.contents, (err) => {
if (err) {
reject(err);
}
resolve();
});
});
});
};

/**
* Copy images from readed from url() to an specific assets destination
Expand All @@ -33,37 +73,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;
return getFile(asset, options, dir, warn)
.then((file) => {
if (!file) return;

const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;
const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;

const targetDir = getTargetDir(dir);
const newAssetBaseDir = getAssetsPath(targetDir, options.assetsPath);
const newAssetPath = path.join(newAssetBaseDir, assetRelativePath);
const newRelativeAssetPath = normalize(
path.relative(targetDir, newAssetPath)
);

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));
};