Skip to content

Commit

Permalink
WIP: Promise for everything
Browse files Browse the repository at this point in the history
* Replace all sync operations to async
  • Loading branch information
Konstantin Mamaev committed Apr 2, 2019
1 parent 1a7496e commit 9851cbf
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 99 deletions.
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
53 changes: 37 additions & 16 deletions src/lib/decl-processor.js
Expand Up @@ -64,7 +64,7 @@ const wrapUrlProcessor = (urlProcessor, result, decl) => {
});

return (asset, dir, option) =>
urlProcessor(asset, dir, option, decl, warn, result, addDependency);
urlProcessor(asset, dir, option, decl, warn, addDependency);
};

/**
Expand All @@ -80,28 +80,36 @@ 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);
};

const resultPromise;
if (Array.isArray(matchedOptions)) {
matchedOptions.forEach((option) => asset.url = process(option));
resultPromise = Promise.resolve();
matchedOptions.forEach((option) => {
resultPromise = resultPromise.then(() => {
return process(option);
});
});
} else {
asset.url = process(matchedOptions);
resultPromise = process(matchedOptions);
}

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

/**
Expand All @@ -110,31 +118,44 @@ 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();

let id = 0;
const promises = [];

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

const marker = `::id${id++}`;

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

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

return `${before}${newUrl}${after}`;
});
decl.value = decl.value.replace(marker, `${before}${newUrl}${after}`);
})
);

return marker;
});

return Promise.all(promises);
};

module.exports = {
replaceUrl,
declProcessor
};

Expand Down
67 changes: 50 additions & 17 deletions src/lib/get-file.js
Expand Up @@ -5,31 +5,64 @@ const mime = require('mime');

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

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

const existFileAsync = (filePath) => {
new Promise((resolve, reject) =>
fs.access(filePath, (err) => {
if (err) {
reject();
}
resolve(path);
})
)
};

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 {
path: filePath,
contents: fs.readFileSync(filePath),
mimeType: mime.getType(filePath)
};
const paths = options.basePath ?
getPathByBasePath(options.basePath, dir.from, asset.pathname) :
[asset.absolutePath];

return oneSuccess(paths.map(path => existFileAsync(path)))
.then(path => readFileAsync(path))
.then(contents => ({
path: filePath,
contents: contents,
mimeType: mime.getType(filePath)
}))
.catch(() => {
warn(`Can't read file '${paths.join()}', ignoring`);
return;
});
};

module.exports = getFile;
Expand Down
93 changes: 64 additions & 29 deletions src/type/copy.js
Expand Up @@ -13,9 +13,46 @@ const getAssetsPath = paths.getAssetsPath;
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);
(options && options.append ? (`${path.basename(file.path, path.extname(file.path))}_`) : '') +
calcHash(file.contents, options) +
path.extname(file.path);

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

const writeFileAsync = (file, src) => {
return new Promise((resolve, reject) => {
fs.open(dest, 'wx', (err, fd) => {
if (err) {
if (err.code === 'EEXIST') {
resolve();
}

reject(err);
}

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

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

/**
* Copy images from readed from url() to an specific assets destination
Expand All @@ -33,37 +70,35 @@ 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;
}

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

if (!file) return;

const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;
module.exports = function processCopy(asset, dir, options, decl, warn, addDependency) {
if (!options.assetsPath && dir.from === dir.to) {
warn('Option `to` of postcss is required, ignoring');

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

mkdirp.sync(path.dirname(newAssetPath));
const newRelativeAssetPath = generateNewPath(file, dir, options);

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

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

return `${newRelativeAssetPath}${asset.search}${asset.hash}`;
return createDirAsync(path.dirname(newAssetPath))
.then(() => writeFileAsync(file, newAssetPath))
.then(() => {
addDependency(file.path);
return `${newRelativeAssetPath}${asset.search}${asset.hash}`;
});
}
);
};
2 changes: 1 addition & 1 deletion src/type/custom.js
Expand Up @@ -6,7 +6,7 @@
* @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);
Expand Down

0 comments on commit 9851cbf

Please sign in to comment.