text: (function (Prism) {
  
  	var javascript = Prism.util.clone(Prism.languages.javascript);
  
  	var space = /(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source;
  	var braces = /(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source;
  	var spread = /(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;
  
  	/**
  	 * @param {string} source
  	 * @param {string} [flags]
  	 */
  	function re(source, flags) {
  		source = source
  			.replace(/<S>/g, function () { return space; })
  			.replace(/<BRACES>/g, function () { return braces; })
  			.replace(/<SPREAD>/g, function () { return spread; });
  		return RegExp(source, flags);
  	}
  
  	spread = re(spread).source;
  
  
  	Prism.languages.jsx = Prism.languages.extend('markup', javascript);
  	Prism.languages.jsx.tag.pattern = re(
  		/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source
  	);
  
  	Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
  	Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i;
  	Prism.languages.jsx.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
  	Prism.languages.jsx.tag.inside['comment'] = javascript['comment'];
  
  	Prism.languages.insertBefore('inside', 'attr-name', {
  		'spread': {
  			pattern: re(/<SPREAD>/.source),
  			inside: Prism.languages.jsx
  		}
  	}, Prism.languages.jsx.tag);
  
  	Prism.languages.insertBefore('inside', 'special-attr', {
  		'script': {
  			// Allow for two levels of nesting
  			pattern: re(/=<BRACES>/.source),
  			inside: {
  				'script-punctuation': {
  					pattern: /^=(?={)/,
  					alias: 'punctuation'
  				},
  				rest: Prism.languages.jsx
  			},
  			'alias': 'language-javascript'
  		}
  	}, Prism.languages.jsx.tag);
  
  	// The following will handle plain text inside tags
  	var stringifyToken = function (token) {
  		if (!token) {
  			return '';
  		}
  		if (typeof token === 'string') {
  			return token;
  		}
  		if (typeof token.content === 'string') {
  			return token.content;
  		}
  		return token.content.map(stringifyToken).join('');
  	};
  
  	var walkTokens = function (tokens) {
  		var openedTags = [];
  		for (var i = 0; i < tokens.length; i++) {
  			var token = tokens[i];
  			var notTagNorBrace = false;
  
  			if (typeof token !== 'string') {
  				if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
  					// We found a tag, now find its kind
  
  					if (token.content[0].content[0].content === '</') {
  						// Closing tag
  						if (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {
  							// Pop matching opening tag
  							openedTags.pop();
  						}
  					} else {
  						if (token.content[token.content.length - 1].content === '/>') {
  							// Autoclosed tag, ignore
  						} else {
  							// Opening tag
  							openedTags.push({
  								tagName: stringifyToken(token.content[0].content[1]),
  								openedBraces: 0
  							});
  						}
  					}
  				} else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {
  
  					// Here we might have entered a JSX context inside a tag
  					openedTags[openedTags.length - 1].openedBraces++;
  
  				} else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {
  
  					// Here we might have left a JSX context inside a tag
  					openedTags[openedTags.length - 1].openedBraces--;
  
  				} else {
  					notTagNorBrace = true;
  				}
  			}
  			if (notTagNorBrace || typeof token === 'string') {
  				if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
  					// Here we are inside a tag, and not inside a JSX context.
  					// That's plain text: drop any tokens matched.
  					var plainText = stringifyToken(token);
  
  					// And merge text with adjacent text
  					if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
  						plainText += stringifyToken(tokens[i + 1]);
  						tokens.splice(i + 1, 1);
  					}
  					if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {
  						plainText = stringifyToken(tokens[i - 1]) + plainText;
  						tokens.splice(i - 1, 1);
  						i--;
  					}
  
  					tokens[i] = new Prism.Token('plain-text', plainText, null, plainText);
  				}
  			}
  
  			if (token.content && typeof token.content !== 'string') {
  				walkTokens(token.content);
  			}
  		}
  	};
  
  	Prism.hooks.add('after-tokenize', function (env) {
  		if (env.language !== 'jsx' && env.language !== 'tsx') {
  			return;
  		}
  		walkTokens(env.tokens);
  	});
  
  }(Prism));
  