diff --git a/flow-libs/posthtml.js.flow b/flow-libs/posthtml.js.flow
index c223570ce2d..6e4f38c8930 100644
--- a/flow-libs/posthtml.js.flow
+++ b/flow-libs/posthtml.js.flow
@@ -10,7 +10,7 @@ declare module 'posthtml' {
declare type PostHTMLNode = {
tag: string,
attrs?: {[string]: string, ...},
- content?: Array,
+ content?: Array,
location?: {
start: {|line: number, column: number|},
end: {|line: number, column: number|},
diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js
index 2b4df44cc38..cb8c1d603e9 100644
--- a/packages/core/integration-tests/test/html.js
+++ b/packages/core/integration-tests/test/html.js
@@ -498,7 +498,7 @@ describe('html', function() {
// minifySvg is false
assert(
html.includes(
- '',
+ '',
),
);
});
@@ -2322,8 +2322,20 @@ describe('html', function() {
});
it('should work with bundle names that have colons in them', async function() {
+ if (process.platform === 'win32') {
+ return;
+ }
+
+ // Windows paths cannot contain colons and will fail to git clone, so write the file here (in memory).
+ await overlayFS.mkdirp(path.join(__dirname, 'integration/url-colon'));
+ await overlayFS.writeFile(
+ path.join(__dirname, 'integration/url-colon/a:b:c.html'),
+ 'Test
',
+ );
+
let b = await bundle(
path.join(__dirname, 'integration/url-colon/relative.html'),
+ {inputFS: overlayFS},
);
assertBundles(b, [
@@ -2342,6 +2354,7 @@ describe('html', function() {
b = await bundle(
path.join(__dirname, 'integration/url-colon/absolute.html'),
+ {inputFS: overlayFS},
);
assertBundles(b, [
@@ -2358,4 +2371,19 @@ describe('html', function() {
output = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8');
assert(output.includes('/a:b:c.html'));
});
+
+ it('should normalize case of SVG elements and attributes when minified', async function() {
+ let b = await bundle(
+ path.join(__dirname, 'integration/html-svg-case/index.html'),
+ {
+ mode: 'production',
+ },
+ );
+
+ let output = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8');
+ assert(output.includes(',
+ inSVG = false,
+) {
+ if (Array.isArray(node)) {
+ for (let i = 0; i < node.length; i++) {
+ // $FlowFixMe
+ node[i] = mapSVG(node[i], inSVG);
+ }
+ } else if (node && typeof node === 'object') {
+ let {tag, attrs} = node;
+ if (inSVG || tag === 'svg') {
+ if (SVG_TAG_NAMES[tag]) {
+ node.tag = SVG_TAG_NAMES[tag];
+ }
+
+ if (attrs) {
+ for (let key in attrs) {
+ if (SVG_ATTRS[key]) {
+ attrs[SVG_ATTRS[key]] = attrs[key];
+ delete attrs[key];
+ }
+ }
+ }
+ }
+
+ if (node.content != null) {
+ mapSVG(node.content, inSVG || tag === 'svg');
+ }
+ }
+
+ return node;
+}
diff --git a/packages/optimizers/htmlnano/src/svgMappings.js b/packages/optimizers/htmlnano/src/svgMappings.js
new file mode 100644
index 00000000000..216114ed4bf
--- /dev/null
+++ b/packages/optimizers/htmlnano/src/svgMappings.js
@@ -0,0 +1,102 @@
+// @flow
+// Based on parse5: https://github.com/inikulin/parse5/blob/252819607421a5741cf745bb60c404f023531b0d/packages/parse5/lib/common/foreign-content.js#L54
+
+export const SVG_TAG_NAMES: {|[string]: string|} = {
+ altglyph: 'altGlyph',
+ altglyphdef: 'altGlyphDef',
+ altglyphitem: 'altGlyphItem',
+ animatecolor: 'animateColor',
+ animatemotion: 'animateMotion',
+ animatetransform: 'animateTransform',
+ clippath: 'clipPath',
+ feblend: 'feBlend',
+ fecolormatrix: 'feColorMatrix',
+ fecomponenttransfer: 'feComponentTransfer',
+ fecomposite: 'feComposite',
+ feconvolvematrix: 'feConvolveMatrix',
+ fediffuselighting: 'feDiffuseLighting',
+ fedisplacementmap: 'feDisplacementMap',
+ fedistantlight: 'feDistantLight',
+ feflood: 'feFlood',
+ fefunca: 'feFuncA',
+ fefuncb: 'feFuncB',
+ fefuncg: 'feFuncG',
+ fefuncr: 'feFuncR',
+ fegaussianblur: 'feGaussianBlur',
+ feimage: 'feImage',
+ femerge: 'feMerge',
+ femergenode: 'feMergeNode',
+ femorphology: 'feMorphology',
+ feoffset: 'feOffset',
+ fepointlight: 'fePointLight',
+ fespecularlighting: 'feSpecularLighting',
+ fespotlight: 'feSpotLight',
+ fetile: 'feTile',
+ feturbulence: 'feTurbulence',
+ foreignobject: 'foreignObject',
+ glyphref: 'glyphRef',
+ lineargradient: 'linearGradient',
+ radialgradient: 'radialGradient',
+ textpath: 'textPath',
+};
+
+export const SVG_ATTRS: {|[string]: string|} = {
+ attributename: 'attributeName',
+ attributetype: 'attributeType',
+ basefrequency: 'baseFrequency',
+ baseprofile: 'baseProfile',
+ calcmode: 'calcMode',
+ clippathunits: 'clipPathUnits',
+ diffuseconstant: 'diffuseConstant',
+ edgemode: 'edgeMode',
+ filterunits: 'filterUnits',
+ glyphref: 'glyphRef',
+ gradienttransform: 'gradientTransform',
+ gradientunits: 'gradientUnits',
+ kernelmatrix: 'kernelMatrix',
+ kernelunitlength: 'kernelUnitLength',
+ keypoints: 'keyPoints',
+ keysplines: 'keySplines',
+ keytimes: 'keyTimes',
+ lengthadjust: 'lengthAdjust',
+ limitingconeangle: 'limitingConeAngle',
+ markerheight: 'markerHeight',
+ markerunits: 'markerUnits',
+ markerwidth: 'markerWidth',
+ maskcontentunits: 'maskContentUnits',
+ maskunits: 'maskUnits',
+ numoctaves: 'numOctaves',
+ pathlength: 'pathLength',
+ patterncontentunits: 'patternContentUnits',
+ patterntransform: 'patternTransform',
+ patternunits: 'patternUnits',
+ pointsatx: 'pointsAtX',
+ pointsaty: 'pointsAtY',
+ pointsatz: 'pointsAtZ',
+ preservealpha: 'preserveAlpha',
+ preserveaspectratio: 'preserveAspectRatio',
+ primitiveunits: 'primitiveUnits',
+ refx: 'refX',
+ refy: 'refY',
+ repeatcount: 'repeatCount',
+ repeatdur: 'repeatDur',
+ requiredextensions: 'requiredExtensions',
+ requiredfeatures: 'requiredFeatures',
+ specularconstant: 'specularConstant',
+ specularexponent: 'specularExponent',
+ spreadmethod: 'spreadMethod',
+ startoffset: 'startOffset',
+ stddeviation: 'stdDeviation',
+ stitchtiles: 'stitchTiles',
+ surfacescale: 'surfaceScale',
+ systemlanguage: 'systemLanguage',
+ tablevalues: 'tableValues',
+ targetx: 'targetX',
+ targety: 'targetY',
+ textlength: 'textLength',
+ viewbox: 'viewBox',
+ viewtarget: 'viewTarget',
+ xchannelselector: 'xChannelSelector',
+ ychannelselector: 'yChannelSelector',
+ zoomandpan: 'zoomAndPan',
+};
diff --git a/packages/transformers/html/src/HTMLTransformer.js b/packages/transformers/html/src/HTMLTransformer.js
index 2bc99afe1a2..13b85e80dce 100644
--- a/packages/transformers/html/src/HTMLTransformer.js
+++ b/packages/transformers/html/src/HTMLTransformer.js
@@ -18,6 +18,7 @@ export default (new Transformer({
type: 'posthtml',
version: '0.4.1',
program: parse(await asset.getCode(), {
+ lowerCaseTags: true,
lowerCaseAttributeNames: true,
sourceLocations: true,
}),