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 1 commit
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
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++}`;
MrMeison marked this conversation as resolved.
Show resolved Hide resolved

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) => {
MrMeison marked this conversation as resolved.
Show resolved Hide resolved
fs.readFile(filePath, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};

const existFileAsync = (filePath) => {
MrMeison marked this conversation as resolved.
Show resolved Hide resolved
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