Skip to content

Commit

Permalink
fix: false positive cycle dectection
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Sep 10, 2019
1 parent fa5b6e3 commit 3fb1cb9
Showing 1 changed file with 102 additions and 84 deletions.
186 changes: 102 additions & 84 deletions src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ function AstroturfLoaderError(
AstroturfLoaderError.prototype = Object.create(Error.prototype);
AstroturfLoaderError.prototype.constructor = AstroturfLoaderError;

function timeout(ms, promise, err) {
return Promise.race([
promise,
new Promise((resolve, reject) => {
setTimeout(() => reject(err), ms);
}),
]);
}

function buildDependencyError(
content,
{ type, identifier, request },
Expand Down Expand Up @@ -149,105 +158,114 @@ module.exports = function loader(content, map, meta) {
const { resourcePath, _compilation: compilation } = this;
const cb = this.async();

if (!compilation[SEEN]) compilation[SEEN] = new Set();
if (!compilation[SEEN]) compilation[SEEN] = new Map();

const resolve = util.promisify((request, done) => {
this.resolve(dirname(resourcePath), request, (err, resource) => {
if (err) {
done(err);
return;
}
const loadModule = util.promisify((request, done) =>
this.loadModule(request, (err, _, __, module) => done(err, module)),
);

if (compilation[SEEN].has(resource)) {
done(
new AstroturfLoaderError(
'A cyclical style interpolation was detected in an interpolated stylesheet or component which is not supported.\n' +
`while importing "${request}" in ${resourcePath}`,
),
);
return;
}
const resolve = util.promisify(this.resolve);

this.loadModule(resource, (err2, _, __, module) => {
// console.log('HERE', args);
done(err2, module);
});
});
});
const buildDependency = async request => {
const resource = await resolve(dirname(resourcePath), request);

return (async () => {
const options = loaderUtils.getOptions(this) || {};
const dependencies = [];
const maybeCycle = compilation[SEEN].has(resource);

function resolveDependency(interpolation, localStyle, node) {
const { identifier, request } = interpolation;
if (!interpolation.identifier) return null;
const { loc } = node;
// It's hard to know if a seen module is due to a cycle or just already done
// I'm sure there is a cleaner way to handle this but IDK what it is, so we bail
// after a second of perceived deadlock
return maybeCycle
? timeout(
1000,
loadModule(resource),
new AstroturfLoaderError(
'A possible cyclical style interpolation was detected in an interpolated stylesheet or component which is not supported.\n' +
`while importing "${request}" in ${resourcePath}`,
),
)
: loadModule(resource);
};

const options = loaderUtils.getOptions(this) || {};
const dependencies = [];

function resolveDependency(interpolation, localStyle, node) {
const { identifier, request } = interpolation;
if (!interpolation.identifier) return null;
const { loc } = node;

const memberProperty = node.property && node.property.name;

const imported = `###ASTROTURF_IMPORTED_${dependencies.length}###`;
const source = `###ASTROTURF_SOURCE_${dependencies.length}###`;

debug(`resolving dependency: ${request}`);
dependencies.push(
buildDependency(request).then(module => {
const style = module.styles.find(s => s.identifier === identifier);

if (!style) {
throw buildDependencyError(content, interpolation, module, loc);
}

debug(`resolved request to: ${style.absoluteFilePath}`);
localStyle.value = localStyle.value
.replace(source, `~${style.absoluteFilePath}`)
.replace(
imported,
style.isStyledComponent ? 'cls1' : memberProperty,
);
}),
);

const memberProperty = node.property && node.property.name;
return { source, imported };
}

const imported = `###ASTROTURF_IMPORTED_${dependencies.length}###`;
const source = `###ASTROTURF_SOURCE_${dependencies.length}###`;
const { styles = [], changeset } = collectStyles(
content,
resourcePath,
resolveDependency,
options,
);

debug(`resolving dependency: ${request}`);
dependencies.push(
resolve(request).then(module => {
const style = module.styles.find(s => s.identifier === identifier);
if (meta) {
meta.styles = styles;
}

if (!style) {
throw buildDependencyError(content, interpolation, module, loc);
}
if (!styles.length) {
return cb(null, content);
}

debug(`resolved request to: ${style.absoluteFilePath}`);
localStyle.value = localStyle.value
.replace(source, `~${style.absoluteFilePath}`)
.replace(
imported,
style.isStyledComponent ? 'cls1' : memberProperty,
);
}),
);
compilation[SEEN].set(resourcePath, styles);
this._module.styles = styles;

return { source, imported };
}
const { styles = [], changeset } = collectStyles(
content,
resourcePath,
resolveDependency,
options,
);
let { emitVirtualFile } = this;

if (meta) {
meta.styles = styles;
// The plugin isn't loaded
if (!emitVirtualFile) {
const { compiler } = compilation;
let plugin = compiler[LOADER_PLUGIN];
if (!plugin) {
debug('adding plugin to compiiler');
plugin = VirtualModulePlugin.bootstrap(compilation);
compiler[LOADER_PLUGIN] = plugin;
}
emitVirtualFile = plugin.addFile;
}

if (!styles.length) return content;

compilation[SEEN].add(resourcePath);

this._module.styles = styles;

let { emitVirtualFile } = this;

// The plugin isn't loaded
if (!emitVirtualFile) {
const { compiler } = compilation;
let plugin = compiler[LOADER_PLUGIN];
if (!plugin) {
debug('adding plugin to compiiler');
plugin = VirtualModulePlugin.bootstrap(compilation);
compiler[LOADER_PLUGIN] = plugin;
}
emitVirtualFile = plugin.addFile;
}
// console.log('WAIT', resourcePath);
return Promise.all(dependencies)
.then(() => {
styles.forEach(style => {
const mtime = emitVirtualFile(style.absoluteFilePath, style.value);
compilation.fileTimestamps.set(style.absoluteFilePath, +mtime);
});

await Promise.all(dependencies);
const result = replaceStyleTemplates(content, changeset);

styles.forEach(style => {
const mtime = emitVirtualFile(style.absoluteFilePath, style.value);
compilation.fileTimestamps.set(style.absoluteFilePath, +mtime);
});
cb(null, result);
})
.catch(cb);

return replaceStyleTemplates(content, changeset);
})().then(result => cb(null, result), cb);
// console.log('DONE', resourcePath);
};

0 comments on commit 3fb1cb9

Please sign in to comment.