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

Release 1.8 (formerly 1.7.1) #9942

Merged
merged 453 commits into from Oct 5, 2018
Commits
Jump to file or symbol
Failed to load files and symbols.
+68 −41
Diff settings

Always

Just for now

Viewing a subset of changes. View all

Cache minified CSS in minifyCssFiles rather than relying on plugins.

Static CSS gets merged in development as well as production by
ClientTarget#minifyCss; the only difference is that the CSS is also
minified in production. This development-time merging is especially
expensive because it involves source maps, and happens on every rebuild of
the app. That's why ClientTarget#minifyCss consistently shows up in
METEOR_PROFILE output for rebuilds.

The standard-minifier-css package tries to cache the merging based on the
hashes of the input files:
https://github.com/meteor/meteor/blob/8a562523a44e33fd67278fb2e70798a2090df1ea/packages/standard-minifier-css/plugin/minify-css.js#L58-L62

However, not all CSS minifier plugins use a strategy like this, and
https://atmospherejs.com/juliancwirko/postcss is an increasingly popular
one that does not.

The time has come to move this caching logic into the Meteor build tool
itself, so that CSS minifier plugins no longer have to worry about basic
caching, and everyone who uses CSS minifiers with Meteor will benefit.

This implementation leverages Meteor's powerful optimistic caching
techniques to keep previous results in memory only as long as the minifier
object remains reachable.
  • Loading branch information...
benjamn committed Jun 30, 2018
commit 1ed095c36d7b2915872eb0c943dae0c4f870d7e4
Copy path View file
@@ -1090,9 +1090,11 @@ class Target {
file.setTargetPathFromRelPath(
stripLeadingSlash(resource.servePath));
return target.minifyCssFiles(
[file], minifiersByExt.css, minifyMode
).map(file => file.contents("utf8")).join("\n");
return minifyCssFiles([file], {
arch: target.arch,
minifier: minifiersByExt.css,
minifyMode,
}).map(file => file.contents("utf8")).join("\n");
}
});
@@ -1567,45 +1569,11 @@ class ClientTarget extends Target {
// Minify the CSS in this target
minifyCss(minifierDef, minifyMode) {
this.css = this.minifyCssFiles(this.css, minifierDef, minifyMode);
}
minifyCssFiles(files, minifierDef, minifyMode) {
const { arch } = this;
const sources = files.map(file => new CssFile(file, { arch }));
const minifier = minifierDef.userPlugin.processFilesForBundle
.bind(minifierDef.userPlugin);
buildmessage.enterJob('minifying app stylesheet', function () {
try {
const markedMinifier = buildmessage.markBoundary(minifier);
Promise.await(markedMinifier(sources, { minifyMode }));
} catch (e) {
buildmessage.exception(e);
}
this.css = minifyCssFiles(this.css, {
arch: this.arch,
minifier: minifierDef,
minifyMode,
});
return _.flatten(sources.map((source) => {
return source._minifiedFiles.map((file) => {
const newFile = new File({
info: 'minified css',
arch,
data: Buffer.from(file.data, 'utf8')
});
if (file.sourceMap) {
newFile.setSourceMap(file.sourceMap, '/');
}
if (file.path) {
newFile.setUrlFromRelPath(file.path);
newFile.targetPath = file.path;
} else {
newFile.setUrlToHash('.css', '?meteor_css_resource=true');
}
return newFile;
});
}));
}
// Output the finished target to disk
@@ -1804,6 +1772,65 @@ class ClientTarget extends Target {
}
}
const { wrap, defaultMakeCacheKey } = require("optimism");
const minifyCssFiles = Profile("minifyCssFiles", wrap(function (files, {
arch,
minifier,
minifyMode,
}) {
const sources = files.map(file => new CssFile(file, { arch }));
const markedMinifier = buildmessage.markBoundary(
minifier.userPlugin.processFilesForBundle,
minifier.userPlugin,
);
buildmessage.enterJob('minifying app stylesheet', function () {
try {
Promise.await(markedMinifier(sources, { minifyMode }));
} catch (e) {
buildmessage.exception(e);
}
});
return _.flatten(sources.map((source) => {
return source._minifiedFiles.map((file) => {
const newFile = new File({
info: 'minified css',
arch,
data: Buffer.from(file.data, 'utf8')
});
if (file.sourceMap) {
newFile.setSourceMap(file.sourceMap, '/');
}
if (file.path) {
newFile.setUrlFromRelPath(file.path);
newFile.targetPath = file.path;
} else {
newFile.setUrlToHash('.css', '?meteor_css_resource=true');
}
return newFile;
});
}));
}, {
makeCacheKey(files, { arch, minifier, minifyMode }) {
return defaultMakeCacheKey(
minifier,

This comment has been minimized.

@benjamn

benjamn Jun 30, 2018

Member

The caching system uses the minifier object as a key in a WeakMap behind the scenes, so the cache will be invalidated and its memory will be reclaimed whenever a new minifier plugin is constructed. Fortunately, that doesn't happen on every rebuild, unless the implementation of the minifier has changed, requiring it to be rebuilt. In other words, rarely.

arch,
minifyMode,
hashOfFiles(files),
);
}
}));
const { createHash } = require("crypto");
function hashOfFiles(files) {
const hash = createHash("sha1");
files.forEach(file => hash.update(file.hash()).update("\0"));
return hash.digest("hex");
}
// mark methods for profiling
[
'minifyCss',
ProTip! Use n and p to navigate between commits in a pull request.