build(aio): auto-fill width/height to all image tags
Parse all `<img>` tags, during doc-gen, and insert the width and height of the sourceed image, if neither are already specified. Warnings are reported if the `<img>` tag has no `src` attribute or the image cannot be loaded. The work is done in the `addImageDimensions` post-processor, which must be configured with a `basePath` so that it knows where to find the images. Closes #15888
This commit is contained in:
parent
64335d3521
commit
ca17d4f639
|
@ -68,8 +68,10 @@
|
||||||
"firebase-tools": "^3.2.1",
|
"firebase-tools": "^3.2.1",
|
||||||
"fs-extra": "^2.1.2",
|
"fs-extra": "^2.1.2",
|
||||||
"globby": "^6.1.0",
|
"globby": "^6.1.0",
|
||||||
|
"hast-util-is-element": "^1.0.0",
|
||||||
"html": "^1.0.0",
|
"html": "^1.0.0",
|
||||||
"http-server": "^0.9.0",
|
"http-server": "^0.9.0",
|
||||||
|
"image-size": "^0.5.1",
|
||||||
"jasmine-core": "~2.5.2",
|
"jasmine-core": "~2.5.2",
|
||||||
"jasmine-spec-reporter": "~3.2.0",
|
"jasmine-spec-reporter": "~3.2.0",
|
||||||
"jsdom": "^9.12.0",
|
"jsdom": "^9.12.0",
|
||||||
|
@ -94,6 +96,8 @@
|
||||||
"ts-node": "~2.0.0",
|
"ts-node": "~2.0.0",
|
||||||
"tslint": "~4.5.0",
|
"tslint": "~4.5.0",
|
||||||
"typescript": "2.2.0",
|
"typescript": "2.2.0",
|
||||||
|
"unist-util-source": "^1.0.1",
|
||||||
|
"unist-util-visit": "^1.1.1",
|
||||||
"vrsource-tslint-rules": "^4.0.1",
|
"vrsource-tslint-rules": "^4.0.1",
|
||||||
"watchr": "^3.0.1",
|
"watchr": "^3.0.1",
|
||||||
"yargs": "^7.0.2"
|
"yargs": "^7.0.2"
|
||||||
|
|
|
@ -34,6 +34,9 @@ module.exports = new Package('angular-base', [
|
||||||
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
||||||
.factory(require('./readers/json'))
|
.factory(require('./readers/json'))
|
||||||
.factory(require('./services/copyFolder'))
|
.factory(require('./services/copyFolder'))
|
||||||
|
.factory(require('./services/getImageDimensions'))
|
||||||
|
|
||||||
|
.factory(require('./post-processors/add-image-dimensions'))
|
||||||
|
|
||||||
.config(function(checkAnchorLinksProcessor) {
|
.config(function(checkAnchorLinksProcessor) {
|
||||||
// This is disabled here to prevent false negatives for the `docs-watch` task.
|
// This is disabled here to prevent false negatives for the `docs-watch` task.
|
||||||
|
@ -120,9 +123,11 @@ module.exports = new Package('angular-base', [
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.config(function(postProcessHtml) {
|
.config(function(postProcessHtml, addImageDimensions) {
|
||||||
|
addImageDimensions.basePath = path.resolve(AIO_PATH, 'src');
|
||||||
postProcessHtml.plugins = [
|
postProcessHtml.plugins = [
|
||||||
require('./post-processors/autolink-headings')
|
require('./post-processors/autolink-headings'),
|
||||||
|
addImageDimensions
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
40
aio/tools/transforms/angular-base-package/post-processors/add-image-dimensions.js
vendored
Normal file
40
aio/tools/transforms/angular-base-package/post-processors/add-image-dimensions.js
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
const visit = require('unist-util-visit');
|
||||||
|
const is = require('hast-util-is-element');
|
||||||
|
const source = require('unist-util-source');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the width and height of the image to the `img` tag if they are
|
||||||
|
* not already provided. This helps prevent jank when the page is
|
||||||
|
* rendered before the image has downloaded.
|
||||||
|
*
|
||||||
|
* If there is no `src` attribute on an image, or it is not possible
|
||||||
|
* to load the image file indicated by the `src` then a warning is emitted.
|
||||||
|
*/
|
||||||
|
module.exports = function addImageDimensions(getImageDimensions) {
|
||||||
|
return function addImageDimensionsImpl() {
|
||||||
|
return (ast, file) => {
|
||||||
|
visit(ast, node => {
|
||||||
|
|
||||||
|
if (is(node, 'img')) {
|
||||||
|
const props = node.properties;
|
||||||
|
const src = props.src;
|
||||||
|
if (!src) {
|
||||||
|
file.message('Missing src in image tag `' + source(node, file) + '`');
|
||||||
|
} else if (props.width === undefined && props.height === undefined) {
|
||||||
|
try {
|
||||||
|
const dimensions = getImageDimensions(addImageDimensionsImpl.basePath, src);
|
||||||
|
props.width = '' + dimensions.width;
|
||||||
|
props.height = '' + dimensions.height;
|
||||||
|
} catch(e) {
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
file.message('Unable to load src in image tag `' + source(node, file) + '`');
|
||||||
|
} else {
|
||||||
|
file.fail(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,110 @@
|
||||||
|
var createTestPackage = require('../../helpers/test-package');
|
||||||
|
var Dgeni = require('dgeni');
|
||||||
|
|
||||||
|
describe('addImageDimensions post-processor', () => {
|
||||||
|
let processor, getImageDimensionsSpy, addImageDimensions, log;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const testPackage = createTestPackage('angular-base-package')
|
||||||
|
.factory('getImageDimensions', mockGetImageDimensions);
|
||||||
|
const dgeni = new Dgeni([testPackage]);
|
||||||
|
const injector = dgeni.configureInjector();
|
||||||
|
log = injector.get('log');
|
||||||
|
addImageDimensions = injector.get('addImageDimensions');
|
||||||
|
addImageDimensions.basePath = 'base/path';
|
||||||
|
getImageDimensionsSpy = injector.get('getImageDimensions');
|
||||||
|
processor = injector.get('postProcessHtml');
|
||||||
|
processor.docTypes = ['a'];
|
||||||
|
processor.plugins = [addImageDimensions];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the image dimensions into <img> tags', () => {
|
||||||
|
const docs = [{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: `
|
||||||
|
<p>xxx</p>
|
||||||
|
<img src="a/b.jpg">
|
||||||
|
<p>yyy</p>
|
||||||
|
<img src="c/d.png">
|
||||||
|
<p>zzz</p>
|
||||||
|
`
|
||||||
|
}];
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(getImageDimensionsSpy).toHaveBeenCalledWith('base/path', 'a/b.jpg');
|
||||||
|
expect(getImageDimensionsSpy).toHaveBeenCalledWith('base/path', 'c/d.png');
|
||||||
|
expect(docs).toEqual([{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: `
|
||||||
|
<p>xxx</p>
|
||||||
|
<img src="a/b.jpg" width="10" height="20">
|
||||||
|
<p>yyy</p>
|
||||||
|
<img src="c/d.png" width="30" height="40">
|
||||||
|
<p>zzz</p>
|
||||||
|
`
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log a warning for images with no src attribute', () => {
|
||||||
|
const docs = [{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: '<img attr="value">'
|
||||||
|
}];
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(getImageDimensionsSpy).not.toHaveBeenCalled();
|
||||||
|
expect(docs).toEqual([{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: '<img attr="value">'
|
||||||
|
}]);
|
||||||
|
expect(log.warn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log a warning for images whose source cannot be loaded', () => {
|
||||||
|
getImageDimensionsSpy.and.callFake(() => {
|
||||||
|
const error = new Error('no such file or directory');
|
||||||
|
error.code = 'ENOENT';
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
const docs = [{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: '<img src="missing">'
|
||||||
|
}];
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(getImageDimensionsSpy).toHaveBeenCalled();
|
||||||
|
expect(docs).toEqual([{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: '<img src="missing">'
|
||||||
|
}]);
|
||||||
|
expect(log.warn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore images with width or height attributes', () => {
|
||||||
|
const docs = [{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: `
|
||||||
|
<img src="a/b.jpg" width="10">
|
||||||
|
<img src="c/d.jpg" height="10">
|
||||||
|
<img src="e/f.jpg" width="10" height="10">
|
||||||
|
`
|
||||||
|
}];
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(getImageDimensionsSpy).not.toHaveBeenCalled();
|
||||||
|
expect(docs).toEqual([{
|
||||||
|
docType: 'a',
|
||||||
|
renderedContent: `
|
||||||
|
<img src="a/b.jpg" width="10">
|
||||||
|
<img src="c/d.jpg" height="10">
|
||||||
|
<img src="e/f.jpg" width="10" height="10">
|
||||||
|
`
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockGetImageDimensions() {
|
||||||
|
const imageInfo = {
|
||||||
|
'a/b.jpg': { width: 10, height: 20 },
|
||||||
|
'c/d.png': { width: 30, height: 40 },
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line jasmine/no-unsafe-spy
|
||||||
|
return jasmine.createSpy('getImageDimensions')
|
||||||
|
.and.callFake((base, url) => imageInfo[url]);
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
module.exports = function copyContentAssetsProcessor(copyFolder) {
|
module.exports = function copyContentAssetsProcessor(copyFolder) {
|
||||||
return {
|
return {
|
||||||
|
$runBefore: ['postProcessHtml'],
|
||||||
assetMappings: [],
|
assetMappings: [],
|
||||||
$process() {
|
$process() {
|
||||||
this.assetMappings.forEach(map => {
|
this.assetMappings.forEach(map => {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const { resolve } = require('canonical-path');
|
||||||
|
const sizeOf = require('image-size');
|
||||||
|
|
||||||
|
module.exports = function getImageDimensions() {
|
||||||
|
return (basePath, path) => sizeOf(resolve(basePath, path));
|
||||||
|
};
|
|
@ -3283,7 +3283,7 @@ ignorepatterns@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ignorepatterns/-/ignorepatterns-1.1.0.tgz#ac8f436f2239b5dfb66d5f0d3a904a87ac67cc5e"
|
resolved "https://registry.yarnpkg.com/ignorepatterns/-/ignorepatterns-1.1.0.tgz#ac8f436f2239b5dfb66d5f0d3a904a87ac67cc5e"
|
||||||
|
|
||||||
image-size@~0.5.0:
|
image-size@^0.5.1, image-size@~0.5.0:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.1.tgz#28eea8548a4b1443480ddddc1e083ae54652439f"
|
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.1.tgz#28eea8548a4b1443480ddddc1e083ae54652439f"
|
||||||
|
|
||||||
|
@ -7015,13 +7015,19 @@ unist-util-remove-position@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
unist-util-visit "^1.1.0"
|
unist-util-visit "^1.1.0"
|
||||||
|
|
||||||
|
unist-util-source@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-source/-/unist-util-source-1.0.1.tgz#989fb50f8c8508d4cdd629162a38f06d7e08cc29"
|
||||||
|
dependencies:
|
||||||
|
vfile-location "^2.0.1"
|
||||||
|
|
||||||
unist-util-stringify-position@^1.0.0:
|
unist-util-stringify-position@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.0.tgz#e8ba9d6b6af891b5f8336b3a31c63a9dc85c2af0"
|
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.0.tgz#e8ba9d6b6af891b5f8336b3a31c63a9dc85c2af0"
|
||||||
dependencies:
|
dependencies:
|
||||||
has "^1.0.1"
|
has "^1.0.1"
|
||||||
|
|
||||||
unist-util-visit@^1.0.0, unist-util-visit@^1.1.0:
|
unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.1.1.tgz#e917a3b137658b335cb4420c7da2e74d928e4e94"
|
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.1.1.tgz#e917a3b137658b335cb4420c7da2e74d928e4e94"
|
||||||
|
|
||||||
|
@ -7218,7 +7224,7 @@ verror@1.3.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
extsprintf "1.0.2"
|
extsprintf "1.0.2"
|
||||||
|
|
||||||
vfile-location@^2.0.0:
|
vfile-location@^2.0.0, vfile-location@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.1.tgz#0bf8816f732b0f8bd902a56fda4c62c8e935dc52"
|
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.1.tgz#0bf8816f732b0f8bd902a56fda4c62c8e935dc52"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue