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

feat(html): support ie conditional start/end comment #5470

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
80 changes: 80 additions & 0 deletions src/language-html/conditional-comment.js
@@ -0,0 +1,80 @@
"use strict";

// https://css-tricks.com/how-to-create-an-ie-only-stylesheet

// <!--[if ... ]> ... <![endif]-->
const IE_CONDITIONAL_START_END_COMMENT_REGEX = /^(\[if([^\]]*?)\]>)([\s\S]*?)<!\s*\[endif\]$/;
// <!--[if ... ]><!-->
const IE_CONDITIONAL_START_COMMENT_REGEX = /^\[if([^\]]*?)\]><!$/;
// <!--<![endif]-->
const IE_CONDITIONAL_END_COMMENT_REGEX = /^<!\s*\[endif\]$/;

const REGEX_PARSE_TUPLES = [
[IE_CONDITIONAL_START_END_COMMENT_REGEX, parseIeConditionalStartEndComment],
[IE_CONDITIONAL_START_COMMENT_REGEX, parseIeConditionalStartComment],
[IE_CONDITIONAL_END_COMMENT_REGEX, parseIeConditionalEndComment]
];

function parseIeConditionalComment(node, parseHtml) {
if (node.value) {
let match;
for (const [regex, parse] of REGEX_PARSE_TUPLES) {
if ((match = node.value.match(regex))) {
return parse(node, parseHtml, match);
}
}
}
return null;
}

function parseIeConditionalStartEndComment(node, parseHtml, match) {
const [, openingTagSuffix, condition, data] = match;
const offset = "<!--".length + openingTagSuffix.length;
const contentStartSpan = node.sourceSpan.start.moveBy(offset);
const contentEndSpan = contentStartSpan.moveBy(data.length);
const ParseSourceSpan = node.sourceSpan.constructor;
const [complete, children] = (() => {
try {
return [true, parseHtml(data, contentStartSpan).children];
} catch (e) {
const text = {
type: "text",
value: data,
sourceSpan: new ParseSourceSpan(contentStartSpan, contentEndSpan)
};
return [false, [text]];
}
})();
return {
type: "ieConditionalComment",
complete,
children,
condition: condition.trim().replace(/\s+/g, " "),
sourceSpan: node.sourceSpan,
startSourceSpan: new ParseSourceSpan(
node.sourceSpan.start,
contentStartSpan
),
endSourceSpan: new ParseSourceSpan(contentEndSpan, node.sourceSpan.end)
};
}

function parseIeConditionalStartComment(node, parseHtml, match) {
const [, condition] = match;
return {
type: "ieConditionalStartComment",
condition: condition.trim().replace(/\s+/g, " "),
sourceSpan: node.sourceSpan
};
}

function parseIeConditionalEndComment(node /*, parseHtml, match */) {
return {
type: "ieConditionalEndComment",
sourceSpan: node.sourceSpan
};
}

module.exports = {
parseIeConditionalComment
};
45 changes: 1 addition & 44 deletions src/language-html/parser-html.js
Expand Up @@ -5,6 +5,7 @@ const { HTML_ELEMENT_ATTRIBUTES, HTML_TAGS } = require("./utils");
const { hasPragma } = require("./pragma");
const createError = require("../common/parser-create-error");
const { Node } = require("./ast");
const { parseIeConditionalComment } = require("./conditional-comment");

function ngHtmlParser(input, canSelfClose) {
const parser = require("angular-html-parser");
Expand Down Expand Up @@ -240,50 +241,6 @@ function _parse(
});
}

function parseIeConditionalComment(node, parseHtml) {
if (!node.value) {
return null;
}

const match = node.value.match(
/^(\[if([^\]]*?)\]>)([\s\S]*?)<!\s*\[endif\]$/
);

if (!match) {
return null;
}

const [, openingTagSuffix, condition, data] = match;
const offset = "<!--".length + openingTagSuffix.length;
const contentStartSpan = node.sourceSpan.start.moveBy(offset);
const contentEndSpan = contentStartSpan.moveBy(data.length);
const ParseSourceSpan = node.sourceSpan.constructor;
const [complete, children] = (() => {
try {
return [true, parseHtml(data, contentStartSpan).children];
} catch (e) {
const text = {
type: "text",
value: data,
sourceSpan: new ParseSourceSpan(contentStartSpan, contentEndSpan)
};
return [false, [text]];
}
})();
return {
type: "ieConditionalComment",
complete,
children,
condition: condition.trim().replace(/\s+/g, " "),
sourceSpan: node.sourceSpan,
startSourceSpan: new ParseSourceSpan(
node.sourceSpan.start,
contentStartSpan
),
endSourceSpan: new ParseSourceSpan(contentEndSpan, node.sourceSpan.end)
};
}

function locStart(node) {
return node.sourceSpan.start.offset;
}
Expand Down
65 changes: 65 additions & 0 deletions src/language-html/preprocess.js
Expand Up @@ -12,6 +12,7 @@ const {

const PREPROCESS_PIPELINE = [
removeIgnorableFirstLf,
mergeIeConditonalStartEndCommentIntoElementOpeningTag,
mergeCdataIntoText,
extractInterpolation,
extractWhitespaces,
Expand Down Expand Up @@ -52,6 +53,70 @@ function removeIgnorableFirstLf(ast /*, options */) {
});
}

function mergeIeConditonalStartEndCommentIntoElementOpeningTag(
ast /*, options */
) {
/**
* <!--[if ...]><!--><target><!--<![endif]-->
*/
const isTarget = node =>
node.type === "element" &&
node.prev &&
node.prev.type === "ieConditionalStartComment" &&
node.prev.sourceSpan.end.offset === node.startSourceSpan.start.offset &&
node.firstChild &&
node.firstChild.type === "ieConditionalEndComment" &&
node.firstChild.sourceSpan.start.offset === node.startSourceSpan.end.offset;
return ast.map(node => {
if (node.children) {
const isTargetResults = node.children.map(isTarget);
if (isTargetResults.some(Boolean)) {
const newChildren = [];

for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];

if (isTargetResults[i + 1]) {
// ieConditionalStartComment
continue;
}

if (isTargetResults[i]) {
const ieConditionalStartComment = child.prev;
const ieConditionalEndComment = child.firstChild;

const ParseSourceSpan = child.sourceSpan.constructor;
const startSourceSpan = new ParseSourceSpan(
ieConditionalEndComment.sourceSpan.start,
ieConditionalEndComment.sourceSpan.end
);
const sourceSpan = new ParseSourceSpan(
startSourceSpan.start,
child.sourceSpan.end
);

newChildren.push(
child.clone({
condition: ieConditionalStartComment.condition,
sourceSpan,
startSourceSpan,
children: child.children.slice(1)
})
);

continue;
}

newChildren.push(child);
}

return node.clone({ children: newChildren });
}
}
return node;
});
}

function mergeNodeIntoText(ast, shouldMerge, getValue) {
return ast.map(node => {
if (node.children) {
Expand Down
19 changes: 19 additions & 0 deletions src/language-html/printer-html.js
Expand Up @@ -255,6 +255,9 @@ function genericPrint(path, options, print) {
printClosingTag(node)
]);
}
case "ieConditionalStartComment":
case "ieConditionalEndComment":
return concat([printOpeningTagStart(node), printClosingTagEnd(node)]);
case "interpolation":
return concat([
printOpeningTagStart(node),
Expand Down Expand Up @@ -770,11 +773,19 @@ function printOpeningTagStartMarker(node) {
case "comment":
return "<!--";
case "ieConditionalComment":
case "ieConditionalStartComment":
return `<!--[if ${node.condition}`;
case "ieConditionalEndComment":
return `<!--<!`;
case "interpolation":
return "{{";
case "docType":
return "<!DOCTYPE";
case "element":
if (node.condition) {
return `<!--[if ${node.condition}]><!--><${node.rawName}`;
}
// fall through
default:
return `<${node.rawName}`;
}
Expand All @@ -785,6 +796,11 @@ function printOpeningTagEndMarker(node) {
switch (node.type) {
case "ieConditionalComment":
return "]>";
case "element":
if (node.condition) {
return `><!--<![endif]-->`;
}
// fall through
default:
return `>`;
}
Expand All @@ -811,7 +827,10 @@ function printClosingTagEndMarker(node) {
case "comment":
return "-->";
case "ieConditionalComment":
case "ieConditionalEndComment":
return `[endif]-->`;
case "ieConditionalStartComment":
return `]><!-->`;
case "interpolation":
return "}}";
case "element":
Expand Down