diff --git a/README.md b/README.md index 2a77702..f5b1366 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,20 @@ This project provides a way to utilize SVG icons in React-based projects with ea - A **Browserify plugin** that handles the behind-the-scenes aspects of creating your icon library as a require()-able JS module and injecting it into your bundle. This plugin also handles injecting a lightweight React Component which can be utilized to reference graphics. +- A **Webpack plugin** that handles the behind-the-scenes aspects of creating your icon library and injecting it into your bundle. You get the same aforementioned React Icon component that's referenced above, too. + - The aforementioned **React Component**, which transparently handles mapping between SVG and IMG tags in various browsers. It's optimized to be fast, and comes with no external dependencies beyond React itself. - _**Bonus:**_ react-iconpack ships with **over 1,000 ready to use SVG icons** from various different icon projects. Hit the ground running and avoid the tedious task of gathering all the assets you need. +Note +============== +The files in this repository are considered bleeding-edge at the moment. The version currently available on `npm` uses the instructions below; the new version (when released) will feature the Webpack plugin and potentially drop support for generating PNG assets. + +The Webpack plugin currently works, but falls apart when caching builds (either via Webpack or Babel) is turned on. This is because I'm currently investigating the best way to integrate with existing cache solutions instead of rolling my own. If you'd like to help out, clone this repository and install the `devDependencies`, then check out the `webpack-test` folder. The `webpack.config.js` includes an example setup for Webpack integration. + +In terms of caching, the only issue is that the Babel plugin can't be re-run on source code that's cached as already built. Where/how to store accrued SVG nodes becomes complex, and slightly dependent on the upstream cache. Would love to hear ideas from others! + Installation and Usage ============== react-iconpack is currently delivered via npm; if there's some other module vendor system that people want to utilize open a pull request and I'll take a look at it. diff --git a/browserify-test/dist/build.js b/browserify-test/dist/build.js new file mode 100644 index 0000000..ea40a94 --- /dev/null +++ b/browserify-test/dist/build.js @@ -0,0 +1,19351 @@ +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o camelize('background-color') + * < "backgroundColor" + * + * @param {string} string + * @return {string} + */ +function camelize(string) { + return string.replace(_hyphenPattern, function (_, character) { + return character.toUpperCase(); + }); +} + +module.exports = camelize; +},{}],5:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule camelizeStyleName + * @typechecks + */ + +'use strict'; + +var camelize = require('./camelize'); + +var msPattern = /^-ms-/; + +/** + * Camelcases a hyphenated CSS property name, for example: + * + * > camelizeStyleName('background-color') + * < "backgroundColor" + * > camelizeStyleName('-moz-transition') + * < "MozTransition" + * > camelizeStyleName('-ms-transition') + * < "msTransition" + * + * As Andi Smith suggests + * (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix + * is converted to lowercase `ms`. + * + * @param {string} string + * @return {string} + */ +function camelizeStyleName(string) { + return camelize(string.replace(msPattern, 'ms-')); +} + +module.exports = camelizeStyleName; +},{"./camelize":4}],6:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule containsNode + * @typechecks + */ + +'use strict'; + +var isTextNode = require('./isTextNode'); + +/*eslint-disable no-bitwise */ + +/** + * Checks if a given DOM node contains or is another DOM node. + * + * @param {?DOMNode} outerNode Outer DOM node. + * @param {?DOMNode} innerNode Inner DOM node. + * @return {boolean} True if `outerNode` contains or is `innerNode`. + */ +function containsNode(_x, _x2) { + var _again = true; + + _function: while (_again) { + var outerNode = _x, + innerNode = _x2; + _again = false; + + if (!outerNode || !innerNode) { + return false; + } else if (outerNode === innerNode) { + return true; + } else if (isTextNode(outerNode)) { + return false; + } else if (isTextNode(innerNode)) { + _x = outerNode; + _x2 = innerNode.parentNode; + _again = true; + continue _function; + } else if (outerNode.contains) { + return outerNode.contains(innerNode); + } else if (outerNode.compareDocumentPosition) { + return !!(outerNode.compareDocumentPosition(innerNode) & 16); + } else { + return false; + } + } +} + +module.exports = containsNode; +},{"./isTextNode":19}],7:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule createArrayFromMixed + * @typechecks + */ + +'use strict'; + +var toArray = require('./toArray'); + +/** + * Perform a heuristic test to determine if an object is "array-like". + * + * A monk asked Joshu, a Zen master, "Has a dog Buddha nature?" + * Joshu replied: "Mu." + * + * This function determines if its argument has "array nature": it returns + * true if the argument is an actual array, an `arguments' object, or an + * HTMLCollection (e.g. node.childNodes or node.getElementsByTagName()). + * + * It will return false for other array-like objects like Filelist. + * + * @param {*} obj + * @return {boolean} + */ +function hasArrayNature(obj) { + return( + // not null/false + !!obj && ( + // arrays are objects, NodeLists are functions in Safari + typeof obj == 'object' || typeof obj == 'function') && + // quacks like an array + 'length' in obj && + // not window + !('setInterval' in obj) && + // no DOM node should be considered an array-like + // a 'select' element has 'length' and 'item' properties on IE8 + typeof obj.nodeType != 'number' && ( + // a real array + Array.isArray(obj) || + // arguments + 'callee' in obj || + // HTMLCollection/NodeList + 'item' in obj) + ); +} + +/** + * Ensure that the argument is an array by wrapping it in an array if it is not. + * Creates a copy of the argument if it is already an array. + * + * This is mostly useful idiomatically: + * + * var createArrayFromMixed = require('createArrayFromMixed'); + * + * function takesOneOrMoreThings(things) { + * things = createArrayFromMixed(things); + * ... + * } + * + * This allows you to treat `things' as an array, but accept scalars in the API. + * + * If you need to convert an array-like object, like `arguments`, into an array + * use toArray instead. + * + * @param {*} obj + * @return {array} + */ +function createArrayFromMixed(obj) { + if (!hasArrayNature(obj)) { + return [obj]; + } else if (Array.isArray(obj)) { + return obj.slice(); + } else { + return toArray(obj); + } +} + +module.exports = createArrayFromMixed; +},{"./toArray":27}],8:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule createNodesFromMarkup + * @typechecks + */ + +/*eslint-disable fb-www/unsafe-html*/ + +'use strict'; + +var ExecutionEnvironment = require('./ExecutionEnvironment'); + +var createArrayFromMixed = require('./createArrayFromMixed'); +var getMarkupWrap = require('./getMarkupWrap'); +var invariant = require('./invariant'); + +/** + * Dummy container used to render all markup. + */ +var dummyNode = ExecutionEnvironment.canUseDOM ? document.createElement('div') : null; + +/** + * Pattern used by `getNodeName`. + */ +var nodeNamePattern = /^\s*<(\w+)/; + +/** + * Extracts the `nodeName` of the first element in a string of markup. + * + * @param {string} markup String of markup. + * @return {?string} Node name of the supplied markup. + */ +function getNodeName(markup) { + var nodeNameMatch = markup.match(nodeNamePattern); + return nodeNameMatch && nodeNameMatch[1].toLowerCase(); +} + +/** + * Creates an array containing the nodes rendered from the supplied markup. The + * optionally supplied `handleScript` function will be invoked once for each + * + + diff --git a/browserify-test/src/index.js b/browserify-test/src/index.js new file mode 100644 index 0000000..38308af --- /dev/null +++ b/browserify-test/src/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import {render} from 'react-dom'; +import Icon from 'react-iconpack'; + + +class TestComponent extends React.Component { + render() { + return ; + } +}; + + +render(, document.getElementById('test-node')); diff --git a/components/react-iconpack-icons.js b/components/react-iconpack-icons.js new file mode 100644 index 0000000..0d20d63 --- /dev/null +++ b/components/react-iconpack-icons.js @@ -0,0 +1 @@ +module.exports = {react_iconpack_icons: {}}; diff --git a/components/svg.js b/components/svg.js index 9451027..b6beb4b 100644 --- a/components/svg.js +++ b/components/svg.js @@ -1,21 +1,20 @@ /** * This is a React JSX tag that will take care of managing injecting the * icons properly after they've been compiled into your bundle. As long as you're - * using the Browserify plugin this will "just work" - if you'd like to see plugins + * using the Browserify or Webpack plugin this will "just work" - if you'd like to see plugins * for other platforms, submit a pull request! * * Note that this file is liberally commented; React is pretty intuitive but I prefer * if people can easily decipher what I'm trying to accomplish and learn from it. You * may have a different style, but try to follow it here if you modify this. * - * @author Ryan McGrath , contributors (see repo) + * @author Ryan McGrath , contributors (see repo) * @license MIT * @repo https://github.com/ryanmcgrath/react-iconpack/ */ var React = require('react'); - icons = require('react-iconpack-icons'); - +var icons = require('react-iconpack-icons'); /** * A very basic and simple wrapper around console.warn, for debug purposes. @@ -29,8 +28,7 @@ var warn = function(msg) { /** - * The main JSX tag you'll be wanting. Depending on which mode your bundle is - * in, it will handle injecting either an tag or an tag. For any global + * The main JSX tag you'll be wanting. For any global * styling needs you can safely target the "react-iconpack-icon" class in CSS - you * can also add your own, of course, but there's one there for convenience. * @@ -53,8 +51,7 @@ module.exports = React.createClass({ title: '', desc: '', - // These are only used for SVGs as the Browser will do magic by default - // for PNGs. Experienced SVG users can override this as necessary but I'm + // Experienced SVG users can override this as necessary but I'm // interested in providing an easier solution, not the slight headache that // SVG can be. // @@ -119,10 +116,10 @@ module.exports = React.createClass({ */ shouldComponentUpdate: function(nextProps, nextState) { var p = this.props, - np = nextProps, // lol + np = nextProps, k; - if(this.props === nextProps) // Shallow reference + if(this.props === np) return false; for(k in p) @@ -137,67 +134,12 @@ module.exports = React.createClass({ }, /** - * This needs no documentation, it's a render method. - * - * @returns {Object} A JSX "SVG" tag configured based on the properties assigned. - */ - render: function() { - if(icons.mode === 'png') - return this.renderAsPNG(); - else - return this.renderAsSVG(); - }, - - /** - * This seems a little heavy to do here, yes, but I think it's the right - * way to go about it - if anyone has better ideas please feel free to open - * an issue or submit a pull request. - * - * Basically, for an tag we need to shuffle some attributes around. We - * also want to make this accessible, if possible - ideally by moving over the - * accessibility attributes from the typical approach to here. We also - * need to move the uri attribute, and considering it's a base64 PNG I don't - * feel like arbitrarily copying the data to the state and duplicating it. - * - * In reality this might be overkill, but hey, it works fine in my opinion. - * Pull request it if it bothers you. We optimize in shouldComponentUpdate - * anyway to make it so this hopefully won't be re-hit too much. - * - * @returns {Object} A JSX Img tag with SVG attributes shuffled to match. - */ - renderAsPNG: function() { - var props = { - src: ['data:image/png;base64,', icons.icons[this.props.uri]].join(''), - alt: this.props.title + ': ' + this.props.desc, - 'aria-labeledby': 'alt' - }; - - // We don't want to copy these over. This is also rather naive; there's little - // reason anyone should be passing Objects or Arrays or whatever the kids are - // into these days into this tag, but if they do, it has the potential to get - // stupid. - for(var prop in this.props) { - if( - prop === 'src' || prop === 'title' || - prop === 'desc' || prop === 'aria-labeledby' - ) continue; - - if(prop === 'className') - props.className = 'react-iconpack-icon ' + this.props.className; - - props[prop] = this.props[prop]; - } - - return React.createElement('img', React.__spread({}, props)); - }, - - /** - * This is, in comparison, much more straightforward. We provide some default stuff + * We provide some default stuff * that makes working with an SVG tag a bit more like working with an IMG tag. * * @returns {Object} A JSX SVG tag configured and what not. */ - renderAsSVG: function() { + render: function() { var path, accessibility, icon, diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..3571947 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,31 @@ +var gulp = require('gulp'), + fs = require('fs'), + browserify = require('browserify'), + iconpacker = require('react-iconpack')('browserify'); + + +var compile = function() { + var bundler = browserify('./browserify-test/src/index.js', { + debug: true, + }); + + iconpacker.attachBrowserifyInjector(bundler, { + verbose: true + }); + + bundler.require('react').require('react-dom').transform('babelify', { + presets: ['es2015', 'react'], + plugins: ['react-iconpack'] + }); + + bundler.bundle() + .on('error', function(err) { + console.error('Failure! ' + err); + this.emit('end'); + }) + .pipe(fs.createWriteStream('./browserify-test/dist/build.js')); +}; + + +gulp.task('compile', compile); +gulp.task('default', ['compile']); diff --git a/index.js b/index.js index 8805008..fa1462e 100644 --- a/index.js +++ b/index.js @@ -2,403 +2,78 @@ * react-iconpack is a library to make automating SVG/PNG icon usage in React * a bit more streamlined. It consists of a few key components: * - * - A Babel transformer/plugin that determines which SVG + * - A Babel (6) transformer/plugin that determines which SVG * files you actually want to load, straight from your * source. * * - An easy to use React JSX tag that ties in with * this library. * - * - A Browserify plugin that will grab your SVG files, optimize + * - A Webpack plugin that will grab your SVG files, optimize * them, and auto-inject them into your bundle as a require()-able * module. The tag will also transparently handle adding * things to the browser document for you. * - * - Bonus: For browsers that don't support PNG (e.g, IE8) you can - * instruct IconPacker to build a module with base64'd PNGs. - * - * Found a bug, use Webpack, need binary pngs instead of base64? - * Send a pull request. :) + * Found a bug? Send a pull request. :) */ - -var SVGO = require('svgo'), - fs = require('fs'), - async = require('async'), - through = require('through2'), - merge = require('lodash/object/merge'), - ReactSVGComponentFilePath = require.resolve('./components/svg.js'), - im, pngquant; - -// Optional dependencies -try { - pngquant = require('node-pngquant-native'); - im = require('gm').subClass({ - imageMagick: true - }); -} catch(e) { - // Nothing to do here -} +var IconPacker = require('./lib/IconPacker'), + BabelSVGTracker = require('./lib/BabelSVGTracker'), + attachBrowserifyInjector = require('./lib/attachBrowserifyInjector'), + WebpackPlugin = require('./lib/WebpackPlugin'), + packer; /** - * IconPackerer constructor. + * This thing responds differently depending on the environment. * - * @constructor - * @param {Object} options Options - * @return {Object} An instance of IconPacker to do things with. - */ -var IconPacker = function(options) { - if(!(this instanceof IconPacker)) - return new IconPacker(options); - - // This is a reference to the directory where we've got - // provided SVG files held. The user can specify a root directory of - // their own in the options below; we'll always check theirs first, falling - // back to checking ours if there's no hit. - this._internalSVGDirectory = __dirname + '/svgs/'; - - this.opts = merge({ - verbose: false, - mode: 'svg', - - // JSX tags to pick up - JSXTagNames: ['Icon'], - injectReactComponent: true, - iconLibraryNamespace: 'react-iconpack-icons', - - // We provide some default SVGO options that work well for the icons in this - // project; users can override but keep in mind that it's ill-advised at the time - // of writing this to remove the viewBox attributes. This may change in a later release. - // - // (Removing the viewBox can cause very wonky issues in IE, and it helps to size the various icons). - svgSourceDirectory: '', - svgo: { - plugins: [ - {removeViewBox: false}, // See note above ---^ - {removeUselessStrokeAndFill: false}, // This can clobber some icons, just leave it. - {removeEmptyAttrs: false}, // I find this helpful, others may not. - {removeDimensions: true} - ] - }, - - png: { - antialias: true, - density: 1000, - width: 32, - quality: 90, - compressorQuality: [60, 80], - background: 'rgba(0,0,0,0)', - speed: 3, - ie6fix: false - } - }, options); - - // This is our grand database of SVGs. A great @TODO here would - // be some caching, but I've yet to hit speed limitations where I've - // really needed it. Pull requests welcome! - this.SVGS = {}; - - // Basic binding() and such for pieces we use later on in the lifepsan - // of this object. See the respective methods for more details. - this.svgo = new SVGO(this.opts.svgo); - this.JSXSVGTracker = this._SVGTracker.bind(this); - this.BrowserifyInjector = this._BrowserifyInjector.bind(this); - this.readAndOptimizeSVGs = this._readAndOptimizeSVGs.bind(this); - - // PNG mode might not be needed or desired by some people so I don't see a reason - // to make them screw around with extra dependencies. For those who opt into it, - // let's check and ensure that they've got the required modules installed. - // - // gm not being around is an error; pngquant is a nice-to-have but we can technically - // run without it. - if(this.opts.mode === 'png') { - if(!im) - throw new Error( - 'PNG mode, but no trace of Node gm.\n' + - 'You probably want to run:\n\n"npm install gm"\n\n and make sure ' + - 'that ImageMagick is installed with librsvg support. On a Mac ' + - 'with Homebrew, this would be something like:\n\n' + - 'brew install imagemagick --with-librsvg\n\n' - ); - - if(!pngquant) - console.warn( - 'PNG mode, but no trace of pngquant. Compilation will continue ' + - 'but PNGs may be un-optimized. To fix this you probably want to run:' + - '\n\nnpm install node-pngquant-native\n\n' - ) - } - - return this; -}; - - -/** - * Handles async reading in all the SVG files and optimizing them. Really - * nothing too special here - just async.map'd later on down the file. + * - If the engine is "webpack", it returns a function to build a new + * Webpack plugin/IconPacker combo in a way that syntactically makes sense + * in a webpack.config.js scenario. * - * @param {Function} callback Upon completion this junks runs. - */ -IconPacker.prototype._readAndOptimizeSVGs = function(key, callback) { - var packer = this, - internalFilePath = this._internalSVGDirectory + key + '.svg', - filePath = packer.opts.svgSourceDirectory + key + '.svg'; - - var onSVGOComplete = function(result) { - packer.SVGS[key] = result; - callback(null, result); - }; - - fs.readFile(filePath, 'utf8', function(error, data) { - if(!error) - return packer.svgo.optimize(data, onSVGOComplete); - - fs.readFile(internalFilePath, 'utf8', function(e, d) { - if(!e) - return packer.svgo.optimize(d, onSVGOComplete); - - console.warn('Warning: Could not load ' + key); - return callback(e, null); - }); - }); -}; - - -/** - * A Babel "plugin/transformer" that just tracks unique - * JSX tags in your source code. Note that this does absolutely no - * modifications - just accumulates the unique SVG uris for the mapping. + * - If the engine is "browserify", then it returns a function to attach a + * handler to a Browserify bundler automagically. * - * @param {Object} babel This will be auto-passed, most likely, by Babel. - * @returns {Object} babel.Transformer used for the babel'ing. You know the one. + * - If .types exists, then it's (most likely) babel trying to load it - babel.types + * is the standard API for babel plugins. We return an tracker plugin in this + * case. + * + * - If no engine is specified, then it returns a Webpack loader. We do this to handle + * actually injecting the icons into the source code, because the Webpack API is really + * not clear on how to extend it like this. + * + * The reason for this scenario is that in a Webpack plugin it's nigh-impossible to + * specify anything other than a simple package import for a plugin (insofar as I can tell). + * This method allows configuration to occur in a way that's easy for people to reason about: + * in either Browserify or Webpack, just add "react-iconpack" to the "plugins" list, and then + * use the appropriate plugin itself and everything happens automatically behind the scenes. + * + * This all relies on how modules work behind the scenes - once called, they're cached and + * return the same stuff on repeated require() calls. This allows us to share 1 packer between + * a Webpack/Browserify plugin and the Babel SVG tracker. The tracker accumulates calls, the + * plugins inject the necessary module code. + * + * @param {String} engine The engine to use, or nothing if you want the Babel plugin. + * @returns {Object} engine A different object depending on how this was called. See above. */ -IconPacker.prototype._SVGTracker = function(babel) { - var packer = this; +module.exports = function(engine) { + if(typeof packer === 'undefined') + packer = new IconPacker({}); - return new babel.Transformer('react-iconpack', { - JSXElement: function JSXElement(node, parent, scope, file) { - if(packer.opts.JSXTagNames.indexOf(node.openingElement.name.name) < 0) - return; - - var attributes = node.openingElement.attributes, - l = attributes.length, - i = 0; - - for(; i < l; i++) { - if(attributes[i].name.name !== 'uri') - continue; - - packer.SVGS[attributes[i].value.value] = 1; - } - } - }); -}; - - -/** - * A Browserify plugin that hooks in to the Browserify build pipeline and injects - * your icons and a tag as a require()-able module. - * - * If you use another module loader or something I'm sure you can probably figure - * it out. - * - * Originally I wanted to find a way to just have Babel shove this in, but I - * couldn't figure out a way to do it cleanly so this works. If you use Webpack - * and would like to see a plugin like this, pull requests are welcome. - * - * Note: you almost never need to call this yourself; you really just wanna pass - * it to Browserify instead. See the full documentation for more details. - * - * @param {Object} browserify An instance of Browserify for this to hook into. - * @param {String} imgType Either "png" or "svg". - * @returns {Object} browserify The instance being operated on. - */ -IconPacker.prototype._BrowserifyInjector = function(browserify, opts) { - var startListeningToThisCompleteMessOfALibraryAgain = function() { - browserify.pipeline.get('pack').unshift(this.compile()); - }.bind(this); - - browserify.external('react-iconpack'); - browserify.external('react-iconpack-icons'); - browserify.on('reset', startListeningToThisCompleteMessOfALibraryAgain); - startListeningToThisCompleteMessOfALibraryAgain(); - return browserify; -}; - - -/** - * Handles actually compiling the SVG (or PNG) assets into a module, then passing - * it into the build stream for inclusion in the final bundle. I'll note that this - * was a massive PITA to decipher how to do - comments are your friends, Node devs. - * - * This is the only method that is "callback-hell"-ish, but I've chosen not to break - * it up because it still fits on one screen of code and is follow-able enough. - * - * @returns {Function} A through2 stream in object mode, which Browserify needs. - */ -IconPacker.prototype.compile = function() { - var packer = this, - write = function(buf, enc, next) { - next(null, buf); + if(engine === 'webpack') { + return function(opts) { + return new WebpackPlugin(packer, opts); }; - - var end = function(next) { - var keys = Object.keys(packer.SVGS), - stream = this; - - async.map(keys, packer.readAndOptimizeSVGs, function(error, results) { - fs.readFile(ReactSVGComponentFilePath, 'utf8', function(e, data) { - stream.push({ - id: 'react-iconpack', - externalRequireName: 'react-iconpack', - standaloneModule: 'react-iconpack', - hasExports: true, - source: data, - - // An empty deps object is important here as we don't want to - // accidentally bundle a second copy of React or the icons as - // they're require()'d in the source. Don't change this. - deps: {} - }); - - var icons = { - id: 'react-iconpack-icons', - externalRequireName: 'react-iconpack-icons', - standaloneModule: 'react-iconpack-icons', - hasExports: true, - deps: {} - }; - - if(packer.opts.mode === 'png') { - packer.compilePNGs.call(packer, function PNGComplete(code) { - icons.source = code; - stream.push(icons); - next(); - }); - } else { - icons.source = packer.compileSVGs.call(packer); - stream.push(icons); - next(); - } - }); - }); - } - - return through.obj(write, end); -}; - - -/** - * Compiles the currently loaded SVGs into a JS module, in preparation for - * injection into the bundle. The outputted code is optimized slightly readability, - * for debugging and so on. SVG and PNG module output are slightly different structure-wise - * as with SVG we attach some extra data (viewBox) that PNGs don't need. - * - * Side note: yes, parsing "HTML/XML/etc" with regex is dumb. This is also totally fine - * for right now though. - * - * @returns {String} The source for this "module" as a String, which Browserify needs. - */ -IconPacker.prototype.compileSVGs = function() { - var keys = Object.keys(this.SVGS), - key, - i = 0, - l = keys.length, - viewBoxRegex = /', '') - .replace(/<\s*svg.*?>/ig, '') + "',\n},\n"; + } else if(engine === 'browserify') { + return { + attachBrowserifyInjector: function(browserify, opts) { + return attachBrowserifyInjector.call(packer, browserify, opts); + } + }; + } else if(engine.types) { // babel.types + return BabelSVGTracker.call(packer, engine); } - return code + '}\n};\n'; + // This should only ever be called for the icons module itself, and for Webpack only. + // With Browserify we're able to just inject things properly. + WebpackPlugin.loader.call(this, engine, packer); }; - - -/** - * Converts the loaded SVGS into PNGs then returns a JS module, in preparation for - * injection into the bundle. - * - * Somewhat confusingly, though, both compile___() methods in this library "return" - * Strings, because Browserify needs that. - * - * @param {Function} hollaback Called when all the PNGs be converted and the code's done. - */ -IconPacker.prototype.compilePNGs = function(hollaback) { - var packer = this, - keys = Object.keys(this.SVGS); - - async.map(keys, packer.convertSVGtoPNG.bind(packer), function(error, pngs) { - var i = 0, - l = keys.length, - code = 'module.exports = {\n mode: "png",\n icons: {\n'; - - for(; i < l; i++) { - code += ' "' + keys[i] + '": "' + pngs[i] + '",\n'; - } - - // Remove that dangling "," from the end I guess. Probably don't need to - // as Babel/et al would handle it but whatever. - hollaback(code.slice(0, -1) + '\n }\n};\n'); - }); -}; - - -/** - * A method that uses ImageMagick to convert SVGs to PNGs. This scratches a personal - * itch of the author, who really doesn't feel like running a headless browser in the - * background for a task as simple as converting a bunch of images. - * - * If you experience bad quality conversions with ImageMagick, you likely need to - * install it with librsvg support. For instance, on a Mac with homebrew: - * - * brew install imagemagick --with-librsvg - * - * Ta-da, you should be getting solid conversions now. If you routinely have issues - * with this you should be able to easily monkeypatch this to use something like - * Phantom or... something. See the docs for details. - * - * @param {String} key A key for the internal SVGS object. - * @param {Function} callback A callback that runs when the PNG is ready. - */ -IconPacker.prototype.convertSVGtoPNG = function(key, callback) { - var packer = this, - xml = '', - patch = ' + * JSX tags in your source code. Note that this does absolutely no + * modifications - just accumulates the unique SVG uris for the mapping. + * + * @param {Object} babel This will be auto-passed, most likely, by Babel. + * @returns {Object} babel.Transformer used for the babel'ing. You know the one. + */ +module.exports = function(babel) { + var _packer = this; + + return { + visitor: { + JSXElement: function JSXElement(node, parent, scope, file) { + if(_packer.opts.JSXTagNames.indexOf(node.node.openingElement.name.name) < 0) + return; + + var attributes = node.node.openingElement.attributes, + l = attributes.length, + i = 0; + + for(; i < l; i++) { + if(attributes[i].name.name !== 'uri') + continue; + _packer.SVGS[attributes[i].value.value] = 1; + } + } + } + }; +}; diff --git a/lib/IconPacker.js b/lib/IconPacker.js new file mode 100644 index 0000000..874895f --- /dev/null +++ b/lib/IconPacker.js @@ -0,0 +1,214 @@ +/** + * iconpacker.js + * + * This just encapsulates the core icon-packer functionality. index.js + * is the main entry point to care about insofar as usage. + * + * @author Ryan McGrath ', '') + .replace(/<\s*svg.*?>/ig, '') : "") + "',\n},\n"; + } + + return code + '}\n};\n'; +}; + +module.exports = IconPacker; diff --git a/lib/WebpackPlugin.js b/lib/WebpackPlugin.js new file mode 100644 index 0000000..8e0cc99 --- /dev/null +++ b/lib/WebpackPlugin.js @@ -0,0 +1,103 @@ +/** + * WebpackPlugin.js + * + * The Webpack plugin for react-iconpack. Manages intercepting the + * build/loader/etc processes and injecting the SVG icon set as a module. + * + * @author Ryan McGrath + */ +var path = require('path'), + ReactSVGComponentFilePath = require.resolve('../components/svg.js'), + IconStubComponentPath = require.resolve('../components/react-iconpack-icons.js'); + + +/** + * WebpackPlugin + * + * Accepts a packer, and options for configuring the packer instance itself. + * @Note: You likely don't want this, but the API in index.js. + * + * @param {Object} packer An instance of IconPacker (see lib/IconPacker.js). + * @param {Object} opts Options for configuring IconPacker (for certain use cases it's ideal to update here). + * @returns {Object} WebpackPlugin A new Webpack plugin. + */ +var WebpackPlugin = function(packer, opts) { + this.packer = packer; + this.packer.update(opts); + return this; +}; + + +/** + * WebpackPlugin.loader() + * + * This is Webpack-specific functionality, which is why it's here - not looking + * to overly pollute index.js. This should be .call()'d with the loader context from + * index.js, along with the options. + * + * @param {Object} engine The engine object from index.js, which is just the source code. + * @param {Object} packer The packer object from index.js, shared amongst everything. + * @returns {void} + */ +WebpackPlugin.loader = function(engine, packer) { + var callback = this.async(); + packer.compileForWebpack(function(source) { + callback(null, engine.replace('module.exports = {react_iconpack_icons: {}};', source)); + }); +}; + + +/** + * This prototype.apply chain satisfies the Webpack plugin API. To be quite honest, the + * documentation surrounding Webpack plugin development is (almost, but not quite) as bad + * as Browserify. Really annoying to decipher. + * + * What we essentially do here is the following: + * + * - With each attempt to resolve a module, we check to see if it's one of our + * react-icon-* modules. With both of them, we redirect the request to a file in + * the component directory. This stops Webpack from blowing up and lets us provide + * a nicer API. + * + * - After module resolution is done, we check to see if the one being passed is + * the icons file. We specifically want to apply a loader (our index.js file) to + * replace the module source code with our true icons source code. + * + * This takes advantage of how Webpack consumes loaders. I originally wanted to make use + * of the compilation.addModule call but I could not figure out that undocumented... thing for + * the life of me (creating a RawModule and adding it did nothing). + */ +WebpackPlugin.prototype.apply = function(compiler) { + var packer = this.packer; + + compiler.plugin('normal-module-factory', function(nmf) { + nmf.plugin('before-resolve', function(result, callback) { + if(!result) + return callback(); + + // We'll patch them in later... (see index.js) + if(/react-iconpack-icons$/.test(result.request)) + result.request = IconStubComponentPath; + + // Inject react-iconpack Component + // To do this we actually want to redirect the require statement directive + // to the proper file~ + if(/react-iconpack$/.test(result.request)) + result.request = ReactSVGComponentFilePath; + + callback(null, result); + }); + + // This callback function parameter isn't even in the damn docs, just guessed at it... + // At any rate, if the icons module is detected we push our loader to the front of it + // What's nice is that we can do it ourselves and not require the user to do more configuration + // Note: this is called for every module, so the check is important. + nmf.plugin('after-resolve', function(data, callback) { + if(/react-iconpack-icons\.js/.test(data.request)) + data.loaders.unshift(path.join(__dirname, '../index.js')); + callback(null, data); + }); + }); +}; + +module.exports = WebpackPlugin; diff --git a/lib/attachBrowserifyInjector.js b/lib/attachBrowserifyInjector.js new file mode 100644 index 0000000..615f31f --- /dev/null +++ b/lib/attachBrowserifyInjector.js @@ -0,0 +1,27 @@ +/** + * A Browserify plugin that hooks in to the Browserify build pipeline and injects + * your icons and a tag as a require()-able module. + * + * Originally I wanted to find a way to just have Babel shove this in, but I + * couldn't figure out a way to do it cleanly so this works. + * + * Note: you almost never need to call this yourself; you really just wanna pass + * it to Browserify instead. See the full documentation for more details. + * + * @param {Object} browserify An instance of Browserify for this to hook into. + * @param {Object} opts Options for the icon packer instance itself. + * @returns {Object} browserify The instance being operated on. + */ +module.exports = function(browserify, opts) { + this.update(opts); + + var startListeningToThisCompleteMessOfALibraryAgain = function() { + browserify.pipeline.get('pack').unshift(this.compileForBrowserify()); + }.bind(this); + + browserify.external('react-iconpack'); + browserify.external('react-iconpack-icons'); + browserify.on('reset', startListeningToThisCompleteMessOfALibraryAgain); + startListeningToThisCompleteMessOfALibraryAgain(); + return browserify; +}; diff --git a/lib/cache/fs-cache.js b/lib/cache/fs-cache.js new file mode 100644 index 0000000..3e3d17f --- /dev/null +++ b/lib/cache/fs-cache.js @@ -0,0 +1,136 @@ +'use strict'; + +/** + * Filesystem cache + * + * Given a file and a transform function, cache the result into files + * or retrieve the previously cached files if the given file is already known. + * + * Note: This is shamelessly lifted and modified from babel-loader's cache. See + * https://github.com/babel/babel-loader/blob/master/lib/fs-cache.js + * -- Ryan McGrath, who'd rather not reinvent the wheel + * + * @see https://github.com/babel/babel-loader/issues/34 + * @see https://github.com/babel/babel-loader/pull/41 + */ +var crypto = require('crypto'); +var mkdirp = require('mkdirp'); +var fs = require('fs'); +var os = require('os'); +var path = require('path'); +var zlib = require('zlib'); + +/** + * Read the contents from the compressed file. + * + * @async + * @params {String} filename + * @params {Function} callback + */ +var read = function(filename, callback) { + return fs.readFile(filename, function(err, data) { + if(err) + return callback(err); + + return zlib.gunzip(data, function(err, content) { + var result = {}; + + if(err) + return callback(err); + + try { + result = JSON.parse(content); + } catch (e) { + return callback(e); + } + + return callback(null, result); + }); + }); +}; + + +/** + * Write contents into a compressed file. + * + * @async + * @params {String} filename + * @params {String} result + * @params {Function} callback + */ +var write = function(filename, result, callback) { + var content = JSON.stringify(result); + + return zlib.gzip(content, function(err, data) { + if(err) + return callback(err); + return fs.writeFile(filename, data, callback); + }); +}; + + +/** + * Build the filename for the cached file + * + * @params {String} source File source code + * @params {Object} options Options used + * + * @return {String} + */ +var filename = function(source, identifier, options) { + var hash = crypto.createHash('SHA1'); + var contents = JSON.stringify({ + source: source, + identifier: identifier, + }); + + hash.end(contents); + return hash.read().toString('hex') + '.json.gzip'; +}; + +/** + * Retrieve file from cache, or create a new one for future reads + * + * @async + * @param {Object} params + * @param {String} params.identifier Unique identifier to bust cache + * @param {String} params.source Original contents of the file to be cached + * @param {Function} callback + * + * @example + * + * cache({ + * identifier: '', + * source: *source code from file*, + * }, function(err, result) { + * + * }); + */ +var cache = module.exports = function(params, callback) { + // Spread params into named variables + // Forgive user whenever possible + var source = params.source; + var identifier = params.identifier; + var directory = os.tmpdir(); + var file = path.join(directory, filename(source, identifier)); + + // Make sure the directory exists. + return mkdirp(directory, function(err) { + if(err) + return callback(err); + + return read(file, function(err, content) { + var result = {}; + + // No errors mean that the file was previously cached + // we just need to return it + if(!err) + return callback(null, content); + + // return it to the user asap and write it in cache + return write(file, result, function(err) { + return callback(err, result); + }); + }); + }); +}; diff --git a/package.json b/package.json index 9f1aa3d..469f3b6 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,56 @@ { - "name": "react-iconpack", - "description": "A React Component for handling SVGs coupled with Babel and Browserify plugins to only bundle the SVGs you use.", - "author": "Ryan McGrath", - "version": "1.1.4", - "license": "MIT", - - "dependencies": { - "svgo": "^0.5.3", - "lodash": "*", - "through2": "^2.0.0", - "async": "^1.4.0" - }, - - "devDependencies": { - "jest": "", - "jasmine-node": "" - }, - "scripts": {"test": "jest"}, - - "keywords": [ - "react", - "react-component", - "browserify-plugin", - "babel-plugin", - "browserify", - "babel", - "svg", - "icons", - "polymer" - ], - - "maintainers": [{ - "name": "Ryan McGrath", - "email": "ryan@venodesigns.net", - "web": "http://venodesigns.net/" - }], - - "repository": { - "type": "git", - "url": "https://github.com/ryanmcgrath/react-iconpack.git" - }, - "bugs": {"url": "https://github.com/ryanmcgrath/react-iconpack/issues"} + "name": "react-iconpack", + "description": "A React Component for handling SVGs coupled with Babel and Browserify plugins to only bundle the SVGs you use.", + "author": "Ryan McGrath", + "main": "index.js", + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "async": "^1.4.0", + "lodash": "*", + "svgo": "^0.5.3", + "through2": "^2.0.0" + }, + "devDependencies": { + "babel-core": "^6.7.2", + "babel-loader": "^6.2.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babelify": "^7.2.0", + "browserify": "^13.0.0", + "gulp": "^3.9.1", + "jasmine-node": "", + "jest": "", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "webpack": "^1.12.14" + }, + "scripts": { + "test": "jest" + }, + "keywords": [ + "react", + "react-component", + "browserify-plugin", + "babel-plugin", + "browserify", + "babel", + "svg", + "icons", + "polymer" + ], + "maintainers": [ + { + "name": "Ryan McGrath", + "email": "ryan@venodesigns.net", + "web": "http://venodesigns.net/" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ryanmcgrath/react-iconpack.git" + }, + "bugs": { + "url": "https://github.com/ryanmcgrath/react-iconpack/issues" + } } diff --git a/webpack-test/dist/build.js b/webpack-test/dist/build.js new file mode 100644 index 0000000..a7f149b --- /dev/null +++ b/webpack-test/dist/build.js @@ -0,0 +1,19928 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = __webpack_require__(1); + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _react = __webpack_require__(2); + + var _react2 = _interopRequireDefault(_react); + + var _reactDom = __webpack_require__(159); + + var _reactIconpack = __webpack_require__(160); + + var _reactIconpack2 = _interopRequireDefault(_reactIconpack); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + //!! + + var TestComponent = function (_React$Component) { + _inherits(TestComponent, _React$Component); + + function TestComponent() { + _classCallCheck(this, TestComponent); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(TestComponent).apply(this, arguments)); + } + + _createClass(TestComponent, [{ + key: 'render', + value: function render() { + return _react2.default.createElement(_reactIconpack2.default, { uri: 'polymer/notification/disc_full', width: '48', height: '48' }); + } + }]); + + return TestComponent; + }(_react2.default.Component); + + ; + + (0, _reactDom.render)(_react2.default.createElement(TestComponent, null), document.getElementById('test-node')); + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + module.exports = __webpack_require__(3); + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule React + */ + + 'use strict'; + + var ReactDOM = __webpack_require__(4); + var ReactDOMServer = __webpack_require__(149); + var ReactIsomorphic = __webpack_require__(153); + + var assign = __webpack_require__(40); + var deprecated = __webpack_require__(158); + + // `version` will be added here by ReactIsomorphic. + var React = {}; + + assign(React, ReactIsomorphic); + + assign(React, { + // ReactDOM + findDOMNode: deprecated('findDOMNode', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.findDOMNode), + render: deprecated('render', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.render), + unmountComponentAtNode: deprecated('unmountComponentAtNode', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.unmountComponentAtNode), + + // ReactDOMServer + renderToString: deprecated('renderToString', 'ReactDOMServer', 'react-dom/server', ReactDOMServer, ReactDOMServer.renderToString), + renderToStaticMarkup: deprecated('renderToStaticMarkup', 'ReactDOMServer', 'react-dom/server', ReactDOMServer, ReactDOMServer.renderToStaticMarkup) + }); + + React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOM; + React.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOMServer; + + module.exports = React; + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOM + */ + + /* globals __REACT_DEVTOOLS_GLOBAL_HOOK__*/ + + 'use strict'; + + var ReactCurrentOwner = __webpack_require__(6); + var ReactDOMTextComponent = __webpack_require__(7); + var ReactDefaultInjection = __webpack_require__(72); + var ReactInstanceHandles = __webpack_require__(46); + var ReactMount = __webpack_require__(29); + var ReactPerf = __webpack_require__(19); + var ReactReconciler = __webpack_require__(51); + var ReactUpdates = __webpack_require__(55); + var ReactVersion = __webpack_require__(147); + + var findDOMNode = __webpack_require__(92); + var renderSubtreeIntoContainer = __webpack_require__(148); + var warning = __webpack_require__(26); + + ReactDefaultInjection.inject(); + + var render = ReactPerf.measure('React', 'render', ReactMount.render); + + var React = { + findDOMNode: findDOMNode, + render: render, + unmountComponentAtNode: ReactMount.unmountComponentAtNode, + version: ReactVersion, + + /* eslint-disable camelcase */ + unstable_batchedUpdates: ReactUpdates.batchedUpdates, + unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer + }; + + // Inject the runtime into a devtools global hook regardless of browser. + // Allows for debugging when the hook is injected on the page. + /* eslint-enable camelcase */ + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ + CurrentOwner: ReactCurrentOwner, + InstanceHandles: ReactInstanceHandles, + Mount: ReactMount, + Reconciler: ReactReconciler, + TextComponent: ReactDOMTextComponent + }); + } + + if (process.env.NODE_ENV !== 'production') { + var ExecutionEnvironment = __webpack_require__(10); + if (ExecutionEnvironment.canUseDOM && window.top === window.self) { + + // First check if devtools is not installed + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { + // If we're in Chrome or Firefox, provide a download link if not installed. + if (navigator.userAgent.indexOf('Chrome') > -1 && navigator.userAgent.indexOf('Edge') === -1 || navigator.userAgent.indexOf('Firefox') > -1) { + console.debug('Download the React DevTools for a better development experience: ' + 'https://fb.me/react-devtools'); + } + } + + // If we're in IE8, check to see if we are in compatibility mode and provide + // information on preventing compatibility mode + var ieCompatibilityMode = document.documentMode && document.documentMode < 8; + + process.env.NODE_ENV !== 'production' ? warning(!ieCompatibilityMode, 'Internet Explorer is running in compatibility mode; please add the ' + 'following tag to your HTML to prevent this from happening: ' + '') : undefined; + + var expectedFeatures = [ + // shims + Array.isArray, Array.prototype.every, Array.prototype.forEach, Array.prototype.indexOf, Array.prototype.map, Date.now, Function.prototype.bind, Object.keys, String.prototype.split, String.prototype.trim, + + // shams + Object.create, Object.freeze]; + + for (var i = 0; i < expectedFeatures.length; i++) { + if (!expectedFeatures[i]) { + console.error('One or more ES5 shim/shams expected by React are not available: ' + 'https://fb.me/react-warning-polyfills'); + break; + } + } + } + } + + module.exports = React; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + // shim for using process in browser + + var process = module.exports = {}; + var queue = []; + var draining = false; + var currentQueue; + var queueIndex = -1; + + function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } + } + + function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); + } + + process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } + }; + + // v8 likes predictible objects + function Item(fun, array) { + this.fun = fun; + this.array = array; + } + Item.prototype.run = function () { + this.fun.apply(null, this.array); + }; + process.title = 'browser'; + process.browser = true; + process.env = {}; + process.argv = []; + process.version = ''; // empty string to avoid regexp issues + process.versions = {}; + + function noop() {} + + process.on = noop; + process.addListener = noop; + process.once = noop; + process.off = noop; + process.removeListener = noop; + process.removeAllListeners = noop; + process.emit = noop; + + process.binding = function (name) { + throw new Error('process.binding is not supported'); + }; + + process.cwd = function () { return '/' }; + process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); + }; + process.umask = function() { return 0; }; + + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCurrentOwner + */ + + 'use strict'; + + /** + * Keeps track of the current owner. + * + * The current owner is the component who should own any components that are + * currently being constructed. + */ + var ReactCurrentOwner = { + + /** + * @internal + * @type {ReactComponent} + */ + current: null + + }; + + module.exports = ReactCurrentOwner; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMTextComponent + * @typechecks static-only + */ + + 'use strict'; + + var DOMChildrenOperations = __webpack_require__(8); + var DOMPropertyOperations = __webpack_require__(23); + var ReactComponentBrowserEnvironment = __webpack_require__(27); + var ReactMount = __webpack_require__(29); + + var assign = __webpack_require__(40); + var escapeTextContentForBrowser = __webpack_require__(22); + var setTextContent = __webpack_require__(21); + var validateDOMNesting = __webpack_require__(71); + + /** + * Text nodes violate a couple assumptions that React makes about components: + * + * - When mounting text into the DOM, adjacent text nodes are merged. + * - Text nodes cannot be assigned a React root ID. + * + * This component is used to wrap strings in elements so that they can undergo + * the same reconciliation that is applied to elements. + * + * TODO: Investigate representing React components in the DOM with text nodes. + * + * @class ReactDOMTextComponent + * @extends ReactComponent + * @internal + */ + var ReactDOMTextComponent = function (props) { + // This constructor and its argument is currently used by mocks. + }; + + assign(ReactDOMTextComponent.prototype, { + + /** + * @param {ReactText} text + * @internal + */ + construct: function (text) { + // TODO: This is really a ReactText (ReactNode), not a ReactElement + this._currentElement = text; + this._stringText = '' + text; + + // Properties + this._rootNodeID = null; + this._mountIndex = 0; + }, + + /** + * Creates the markup for this text node. This node is not intended to have + * any features besides containing text content. + * + * @param {string} rootID DOM ID of the root node. + * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + * @return {string} Markup for this text node. + * @internal + */ + mountComponent: function (rootID, transaction, context) { + if (process.env.NODE_ENV !== 'production') { + if (context[validateDOMNesting.ancestorInfoContextKey]) { + validateDOMNesting('span', null, context[validateDOMNesting.ancestorInfoContextKey]); + } + } + + this._rootNodeID = rootID; + if (transaction.useCreateElement) { + var ownerDocument = context[ReactMount.ownerDocumentContextKey]; + var el = ownerDocument.createElement('span'); + DOMPropertyOperations.setAttributeForID(el, rootID); + // Populate node cache + ReactMount.getID(el); + setTextContent(el, this._stringText); + return el; + } else { + var escapedText = escapeTextContentForBrowser(this._stringText); + + if (transaction.renderToStaticMarkup) { + // Normally we'd wrap this in a `span` for the reasons stated above, but + // since this is a situation where React won't take over (static pages), + // we can simply return the text as it is. + return escapedText; + } + + return '' + escapedText + ''; + } + }, + + /** + * Updates this component by updating the text content. + * + * @param {ReactText} nextText The next text content + * @param {ReactReconcileTransaction} transaction + * @internal + */ + receiveComponent: function (nextText, transaction) { + if (nextText !== this._currentElement) { + this._currentElement = nextText; + var nextStringText = '' + nextText; + if (nextStringText !== this._stringText) { + // TODO: Save this as pending props and use performUpdateIfNecessary + // and/or updateComponent to do the actual update for consistency with + // other component types? + this._stringText = nextStringText; + var node = ReactMount.getNode(this._rootNodeID); + DOMChildrenOperations.updateTextContent(node, nextStringText); + } + } + }, + + unmountComponent: function () { + ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID); + } + + }); + + module.exports = ReactDOMTextComponent; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMChildrenOperations + * @typechecks static-only + */ + + 'use strict'; + + var Danger = __webpack_require__(9); + var ReactMultiChildUpdateTypes = __webpack_require__(17); + var ReactPerf = __webpack_require__(19); + + var setInnerHTML = __webpack_require__(20); + var setTextContent = __webpack_require__(21); + var invariant = __webpack_require__(14); + + /** + * Inserts `childNode` as a child of `parentNode` at the `index`. + * + * @param {DOMElement} parentNode Parent node in which to insert. + * @param {DOMElement} childNode Child node to insert. + * @param {number} index Index at which to insert the child. + * @internal + */ + function insertChildAt(parentNode, childNode, index) { + // By exploiting arrays returning `undefined` for an undefined index, we can + // rely exclusively on `insertBefore(node, null)` instead of also using + // `appendChild(node)`. However, using `undefined` is not allowed by all + // browsers so we must replace it with `null`. + + // fix render order error in safari + // IE8 will throw error when index out of list size. + var beforeChild = index >= parentNode.childNodes.length ? null : parentNode.childNodes.item(index); + + parentNode.insertBefore(childNode, beforeChild); + } + + /** + * Operations for updating with DOM children. + */ + var DOMChildrenOperations = { + + dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, + + updateTextContent: setTextContent, + + /** + * Updates a component's children by processing a series of updates. The + * update configurations are each expected to have a `parentNode` property. + * + * @param {array} updates List of update configurations. + * @param {array} markupList List of markup strings. + * @internal + */ + processUpdates: function (updates, markupList) { + var update; + // Mapping from parent IDs to initial child orderings. + var initialChildren = null; + // List of children that will be moved or removed. + var updatedChildren = null; + + for (var i = 0; i < updates.length; i++) { + update = updates[i]; + if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING || update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { + var updatedIndex = update.fromIndex; + var updatedChild = update.parentNode.childNodes[updatedIndex]; + var parentID = update.parentID; + + !updatedChild ? process.env.NODE_ENV !== 'production' ? invariant(false, 'processUpdates(): Unable to find child %s of element. This ' + 'probably means the DOM was unexpectedly mutated (e.g., by the ' + 'browser), usually due to forgetting a when using tables, ' + 'nesting tags like
,

, or , or using non-SVG elements ' + 'in an parent. Try inspecting the child nodes of the element ' + 'with React ID `%s`.', updatedIndex, parentID) : invariant(false) : undefined; + + initialChildren = initialChildren || {}; + initialChildren[parentID] = initialChildren[parentID] || []; + initialChildren[parentID][updatedIndex] = updatedChild; + + updatedChildren = updatedChildren || []; + updatedChildren.push(updatedChild); + } + } + + var renderedMarkup; + // markupList is either a list of markup or just a list of elements + if (markupList.length && typeof markupList[0] === 'string') { + renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); + } else { + renderedMarkup = markupList; + } + + // Remove updated children first so that `toIndex` is consistent. + if (updatedChildren) { + for (var j = 0; j < updatedChildren.length; j++) { + updatedChildren[j].parentNode.removeChild(updatedChildren[j]); + } + } + + for (var k = 0; k < updates.length; k++) { + update = updates[k]; + switch (update.type) { + case ReactMultiChildUpdateTypes.INSERT_MARKUP: + insertChildAt(update.parentNode, renderedMarkup[update.markupIndex], update.toIndex); + break; + case ReactMultiChildUpdateTypes.MOVE_EXISTING: + insertChildAt(update.parentNode, initialChildren[update.parentID][update.fromIndex], update.toIndex); + break; + case ReactMultiChildUpdateTypes.SET_MARKUP: + setInnerHTML(update.parentNode, update.content); + break; + case ReactMultiChildUpdateTypes.TEXT_CONTENT: + setTextContent(update.parentNode, update.content); + break; + case ReactMultiChildUpdateTypes.REMOVE_NODE: + // Already removed by the for-loop above. + break; + } + } + } + + }; + + ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', { + updateTextContent: 'updateTextContent' + }); + + module.exports = DOMChildrenOperations; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Danger + * @typechecks static-only + */ + + 'use strict'; + + var ExecutionEnvironment = __webpack_require__(10); + + var createNodesFromMarkup = __webpack_require__(11); + var emptyFunction = __webpack_require__(16); + var getMarkupWrap = __webpack_require__(15); + var invariant = __webpack_require__(14); + + var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; + var RESULT_INDEX_ATTR = 'data-danger-index'; + + /** + * Extracts the `nodeName` from a string of markup. + * + * NOTE: Extracting the `nodeName` does not require a regular expression match + * because we make assumptions about React-generated markup (i.e. there are no + * spaces surrounding the opening tag and there is at least one attribute). + * + * @param {string} markup String of markup. + * @return {string} Node name of the supplied markup. + * @see http://jsperf.com/extract-nodename + */ + function getNodeName(markup) { + return markup.substring(1, markup.indexOf(' ')); + } + + var Danger = { + + /** + * Renders markup into an array of nodes. The markup is expected to render + * into a list of root nodes. Also, the length of `resultList` and + * `markupList` should be the same. + * + * @param {array} markupList List of markup strings to render. + * @return {array} List of rendered nodes. + * @internal + */ + dangerouslyRenderMarkup: function (markupList) { + !ExecutionEnvironment.canUseDOM ? process.env.NODE_ENV !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + 'thread. Make sure `window` and `document` are available globally ' + 'before requiring React when unit testing or use ' + 'ReactDOMServer.renderToString for server rendering.') : invariant(false) : undefined; + var nodeName; + var markupByNodeName = {}; + // Group markup by `nodeName` if a wrap is necessary, else by '*'. + for (var i = 0; i < markupList.length; i++) { + !markupList[i] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Missing markup.') : invariant(false) : undefined; + nodeName = getNodeName(markupList[i]); + nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; + markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; + markupByNodeName[nodeName][i] = markupList[i]; + } + var resultList = []; + var resultListAssignmentCount = 0; + for (nodeName in markupByNodeName) { + if (!markupByNodeName.hasOwnProperty(nodeName)) { + continue; + } + var markupListByNodeName = markupByNodeName[nodeName]; + + // This for-in loop skips the holes of the sparse array. The order of + // iteration should follow the order of assignment, which happens to match + // numerical index order, but we don't rely on that. + var resultIndex; + for (resultIndex in markupListByNodeName) { + if (markupListByNodeName.hasOwnProperty(resultIndex)) { + var markup = markupListByNodeName[resultIndex]; + + // Push the requested markup with an additional RESULT_INDEX_ATTR + // attribute. If the markup does not start with a < character, it + // will be discarded below (with an appropriate console.error). + markupListByNodeName[resultIndex] = markup.replace(OPEN_TAG_NAME_EXP, + // This index will be parsed back out below. + '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" '); + } + } + + // Render each group of markup with similar wrapping `nodeName`. + var renderNodes = createNodesFromMarkup(markupListByNodeName.join(''), emptyFunction // Do nothing special with ', '
']; + var trWrap = [3, '', '
']; + + var svgWrap = [1, '', '']; + + var markupWrap = { + '*': [1, '?

'], + + 'area': [1, '', ''], + 'col': [2, '', '
'], + 'legend': [1, '
', '
'], + 'param': [1, '', ''], + 'tr': [2, '', '
'], + + 'optgroup': selectWrap, + 'option': selectWrap, + + 'caption': tableWrap, + 'colgroup': tableWrap, + 'tbody': tableWrap, + 'tfoot': tableWrap, + 'thead': tableWrap, + + 'td': trWrap, + 'th': trWrap + }; + + // Initialize the SVG elements since we know they'll always need to be wrapped + // consistently. If they are created inside a
they will be initialized in + // the wrong namespace (and will not display). + var svgElements = ['circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'text', 'tspan']; + svgElements.forEach(function (nodeName) { + markupWrap[nodeName] = svgWrap; + shouldWrap[nodeName] = true; + }); + + /** + * Gets the markup wrap configuration for the supplied `nodeName`. + * + * NOTE: This lazily detects which wraps are necessary for the current browser. + * + * @param {string} nodeName Lowercase `nodeName`. + * @return {?array} Markup wrap configuration, if applicable. + */ + function getMarkupWrap(nodeName) { + !!!dummyNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Markup wrapping node not initialized') : invariant(false) : undefined; + if (!markupWrap.hasOwnProperty(nodeName)) { + nodeName = '*'; + } + if (!shouldWrap.hasOwnProperty(nodeName)) { + if (nodeName === '*') { + dummyNode.innerHTML = ''; + } else { + dummyNode.innerHTML = '<' + nodeName + '>'; + } + shouldWrap[nodeName] = !dummyNode.firstChild; + } + return shouldWrap[nodeName] ? markupWrap[nodeName] : null; + } + + module.exports = getMarkupWrap; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 16 */ +/***/ function(module, exports) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule emptyFunction + */ + + "use strict"; + + function makeEmptyFunction(arg) { + return function () { + return arg; + }; + } + + /** + * This function accepts and discards inputs; it has no side effects. This is + * primarily useful idiomatically for overridable function endpoints which + * always need to be callable, since JS lacks a null-call idiom ala Cocoa. + */ + function emptyFunction() {} + + emptyFunction.thatReturns = makeEmptyFunction; + emptyFunction.thatReturnsFalse = makeEmptyFunction(false); + emptyFunction.thatReturnsTrue = makeEmptyFunction(true); + emptyFunction.thatReturnsNull = makeEmptyFunction(null); + emptyFunction.thatReturnsThis = function () { + return this; + }; + emptyFunction.thatReturnsArgument = function (arg) { + return arg; + }; + + module.exports = emptyFunction; + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactMultiChildUpdateTypes + */ + + 'use strict'; + + var keyMirror = __webpack_require__(18); + + /** + * When a component's children are updated, a series of update configuration + * objects are created in order to batch and serialize the required changes. + * + * Enumerates all the possible types of update configurations. + * + * @internal + */ + var ReactMultiChildUpdateTypes = keyMirror({ + INSERT_MARKUP: null, + MOVE_EXISTING: null, + REMOVE_NODE: null, + SET_MARKUP: null, + TEXT_CONTENT: null + }); + + module.exports = ReactMultiChildUpdateTypes; + +/***/ }, +/* 18 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule keyMirror + * @typechecks static-only + */ + + 'use strict'; + + var invariant = __webpack_require__(14); + + /** + * Constructs an enumeration with keys equal to their value. + * + * For example: + * + * var COLORS = keyMirror({blue: null, red: null}); + * var myColor = COLORS.blue; + * var isColorValid = !!COLORS[myColor]; + * + * The last line could not be performed if the values of the generated enum were + * not equal to their keys. + * + * Input: {key1: val1, key2: val2} + * Output: {key1: key1, key2: key2} + * + * @param {object} obj + * @return {object} + */ + var keyMirror = function (obj) { + var ret = {}; + var key; + !(obj instanceof Object && !Array.isArray(obj)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'keyMirror(...): Argument must be an object.') : invariant(false) : undefined; + for (key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + ret[key] = key; + } + return ret; + }; + + module.exports = keyMirror; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 19 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactPerf + * @typechecks static-only + */ + + 'use strict'; + + /** + * ReactPerf is a general AOP system designed to measure performance. This + * module only has the hooks: see ReactDefaultPerf for the analysis tool. + */ + var ReactPerf = { + /** + * Boolean to enable/disable measurement. Set to false by default to prevent + * accidental logging and perf loss. + */ + enableMeasure: false, + + /** + * Holds onto the measure function in use. By default, don't measure + * anything, but we'll override this if we inject a measure function. + */ + storedMeasure: _noMeasure, + + /** + * @param {object} object + * @param {string} objectName + * @param {object} methodNames + */ + measureMethods: function (object, objectName, methodNames) { + if (process.env.NODE_ENV !== 'production') { + for (var key in methodNames) { + if (!methodNames.hasOwnProperty(key)) { + continue; + } + object[key] = ReactPerf.measure(objectName, methodNames[key], object[key]); + } + } + }, + + /** + * Use this to wrap methods you want to measure. Zero overhead in production. + * + * @param {string} objName + * @param {string} fnName + * @param {function} func + * @return {function} + */ + measure: function (objName, fnName, func) { + if (process.env.NODE_ENV !== 'production') { + var measuredFunc = null; + var wrapper = function () { + if (ReactPerf.enableMeasure) { + if (!measuredFunc) { + measuredFunc = ReactPerf.storedMeasure(objName, fnName, func); + } + return measuredFunc.apply(this, arguments); + } + return func.apply(this, arguments); + }; + wrapper.displayName = objName + '_' + fnName; + return wrapper; + } + return func; + }, + + injection: { + /** + * @param {function} measure + */ + injectMeasure: function (measure) { + ReactPerf.storedMeasure = measure; + } + } + }; + + /** + * Simply passes through the measured function, without measuring it. + * + * @param {string} objName + * @param {string} fnName + * @param {function} func + * @return {function} + */ + function _noMeasure(objName, fnName, func) { + return func; + } + + module.exports = ReactPerf; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 20 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule setInnerHTML + */ + + /* globals MSApp */ + + 'use strict'; + + var ExecutionEnvironment = __webpack_require__(10); + + var WHITESPACE_TEST = /^[ \r\n\t\f]/; + var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/; + + /** + * Set the innerHTML property of a node, ensuring that whitespace is preserved + * even in IE8. + * + * @param {DOMElement} node + * @param {string} html + * @internal + */ + var setInnerHTML = function (node, html) { + node.innerHTML = html; + }; + + // Win8 apps: Allow all html to be inserted + if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) { + setInnerHTML = function (node, html) { + MSApp.execUnsafeLocalFunction(function () { + node.innerHTML = html; + }); + }; + } + + if (ExecutionEnvironment.canUseDOM) { + // IE8: When updating a just created node with innerHTML only leading + // whitespace is removed. When updating an existing node with innerHTML + // whitespace in root TextNodes is also collapsed. + // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html + + // Feature detection; only IE8 is known to behave improperly like this. + var testElement = document.createElement('div'); + testElement.innerHTML = ' '; + if (testElement.innerHTML === '') { + setInnerHTML = function (node, html) { + // Magic theory: IE8 supposedly differentiates between added and updated + // nodes when processing innerHTML, innerHTML on updated nodes suffers + // from worse whitespace behavior. Re-adding a node like this triggers + // the initial and more favorable whitespace behavior. + // TODO: What to do on a detached node? + if (node.parentNode) { + node.parentNode.replaceChild(node, node); + } + + // We also implement a workaround for non-visible tags disappearing into + // thin air on IE8, this only happens if there is no visible text + // in-front of the non-visible tags. Piggyback on the whitespace fix + // and simply check if any non-visible tags appear in the source. + if (WHITESPACE_TEST.test(html) || html[0] === '<' && NONVISIBLE_TEST.test(html)) { + // Recover leading whitespace by temporarily prepending any character. + // \uFEFF has the potential advantage of being zero-width/invisible. + // UglifyJS drops U+FEFF chars when parsing, so use String.fromCharCode + // in hopes that this is preserved even if "\uFEFF" is transformed to + // the actual Unicode character (by Babel, for example). + // https://github.com/mishoo/UglifyJS2/blob/v2.4.20/lib/parse.js#L216 + node.innerHTML = String.fromCharCode(0xFEFF) + html; + + // deleteData leaves an empty `TextNode` which offsets the index of all + // children. Definitely want to avoid this. + var textNode = node.firstChild; + if (textNode.data.length === 1) { + node.removeChild(textNode); + } else { + textNode.deleteData(0, 1); + } + } else { + node.innerHTML = html; + } + }; + } + } + + module.exports = setInnerHTML; + +/***/ }, +/* 21 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule setTextContent + */ + + 'use strict'; + + var ExecutionEnvironment = __webpack_require__(10); + var escapeTextContentForBrowser = __webpack_require__(22); + var setInnerHTML = __webpack_require__(20); + + /** + * Set the textContent property of a node, ensuring that whitespace is preserved + * even in IE8. innerText is a poor substitute for textContent and, among many + * issues, inserts
instead of the literal newline chars. innerHTML behaves + * as it should. + * + * @param {DOMElement} node + * @param {string} text + * @internal + */ + var setTextContent = function (node, text) { + node.textContent = text; + }; + + if (ExecutionEnvironment.canUseDOM) { + if (!('textContent' in document.documentElement)) { + setTextContent = function (node, text) { + setInnerHTML(node, escapeTextContentForBrowser(text)); + }; + } + } + + module.exports = setTextContent; + +/***/ }, +/* 22 */ +/***/ function(module, exports) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule escapeTextContentForBrowser + */ + + 'use strict'; + + var ESCAPE_LOOKUP = { + '&': '&', + '>': '>', + '<': '<', + '"': '"', + '\'': ''' + }; + + var ESCAPE_REGEX = /[&><"']/g; + + function escaper(match) { + return ESCAPE_LOOKUP[match]; + } + + /** + * Escapes text to prevent scripting attacks. + * + * @param {*} text Text value to escape. + * @return {string} An escaped string. + */ + function escapeTextContentForBrowser(text) { + return ('' + text).replace(ESCAPE_REGEX, escaper); + } + + module.exports = escapeTextContentForBrowser; + +/***/ }, +/* 23 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMPropertyOperations + * @typechecks static-only + */ + + 'use strict'; + + var DOMProperty = __webpack_require__(24); + var ReactPerf = __webpack_require__(19); + + var quoteAttributeValueForBrowser = __webpack_require__(25); + var warning = __webpack_require__(26); + + // Simplified subset + var VALID_ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][\w\.\-]*$/; + var illegalAttributeNameCache = {}; + var validatedAttributeNameCache = {}; + + function isAttributeNameSafe(attributeName) { + if (validatedAttributeNameCache.hasOwnProperty(attributeName)) { + return true; + } + if (illegalAttributeNameCache.hasOwnProperty(attributeName)) { + return false; + } + if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { + validatedAttributeNameCache[attributeName] = true; + return true; + } + illegalAttributeNameCache[attributeName] = true; + process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid attribute name: `%s`', attributeName) : undefined; + return false; + } + + function shouldIgnoreValue(propertyInfo, value) { + return value == null || propertyInfo.hasBooleanValue && !value || propertyInfo.hasNumericValue && isNaN(value) || propertyInfo.hasPositiveNumericValue && value < 1 || propertyInfo.hasOverloadedBooleanValue && value === false; + } + + if (process.env.NODE_ENV !== 'production') { + var reactProps = { + children: true, + dangerouslySetInnerHTML: true, + key: true, + ref: true + }; + var warnedProperties = {}; + + var warnUnknownProperty = function (name) { + if (reactProps.hasOwnProperty(name) && reactProps[name] || warnedProperties.hasOwnProperty(name) && warnedProperties[name]) { + return; + } + + warnedProperties[name] = true; + var lowerCasedName = name.toLowerCase(); + + // data-* attributes should be lowercase; suggest the lowercase version + var standardName = DOMProperty.isCustomAttribute(lowerCasedName) ? lowerCasedName : DOMProperty.getPossibleStandardName.hasOwnProperty(lowerCasedName) ? DOMProperty.getPossibleStandardName[lowerCasedName] : null; + + // For now, only warn when we have a suggested correction. This prevents + // logging too much when using transferPropsTo. + process.env.NODE_ENV !== 'production' ? warning(standardName == null, 'Unknown DOM property %s. Did you mean %s?', name, standardName) : undefined; + }; + } + + /** + * Operations for dealing with DOM properties. + */ + var DOMPropertyOperations = { + + /** + * Creates markup for the ID property. + * + * @param {string} id Unescaped ID. + * @return {string} Markup string. + */ + createMarkupForID: function (id) { + return DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id); + }, + + setAttributeForID: function (node, id) { + node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id); + }, + + /** + * Creates markup for a property. + * + * @param {string} name + * @param {*} value + * @return {?string} Markup string, or null if the property was invalid. + */ + createMarkupForProperty: function (name, value) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + if (shouldIgnoreValue(propertyInfo, value)) { + return ''; + } + var attributeName = propertyInfo.attributeName; + if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { + return attributeName + '=""'; + } + return attributeName + '=' + quoteAttributeValueForBrowser(value); + } else if (DOMProperty.isCustomAttribute(name)) { + if (value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); + } else if (process.env.NODE_ENV !== 'production') { + warnUnknownProperty(name); + } + return null; + }, + + /** + * Creates markup for a custom property. + * + * @param {string} name + * @param {*} value + * @return {string} Markup string, or empty string if the property was invalid. + */ + createMarkupForCustomAttribute: function (name, value) { + if (!isAttributeNameSafe(name) || value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); + }, + + /** + * Sets the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + * @param {*} value + */ + setValueForProperty: function (node, name, value) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + var mutationMethod = propertyInfo.mutationMethod; + if (mutationMethod) { + mutationMethod(node, value); + } else if (shouldIgnoreValue(propertyInfo, value)) { + this.deleteValueForProperty(node, name); + } else if (propertyInfo.mustUseAttribute) { + var attributeName = propertyInfo.attributeName; + var namespace = propertyInfo.attributeNamespace; + // `setAttribute` with objects becomes only `[object]` in IE8/9, + // ('' + value) makes it output the correct toString()-value. + if (namespace) { + node.setAttributeNS(namespace, attributeName, '' + value); + } else if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { + node.setAttribute(attributeName, ''); + } else { + node.setAttribute(attributeName, '' + value); + } + } else { + var propName = propertyInfo.propertyName; + // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the + // property type before comparing; only `value` does and is string. + if (!propertyInfo.hasSideEffects || '' + node[propName] !== '' + value) { + // Contrary to `setAttribute`, object properties are properly + // `toString`ed by IE8/9. + node[propName] = value; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + DOMPropertyOperations.setValueForAttribute(node, name, value); + } else if (process.env.NODE_ENV !== 'production') { + warnUnknownProperty(name); + } + }, + + setValueForAttribute: function (node, name, value) { + if (!isAttributeNameSafe(name)) { + return; + } + if (value == null) { + node.removeAttribute(name); + } else { + node.setAttribute(name, '' + value); + } + }, + + /** + * Deletes the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + */ + deleteValueForProperty: function (node, name) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + var mutationMethod = propertyInfo.mutationMethod; + if (mutationMethod) { + mutationMethod(node, undefined); + } else if (propertyInfo.mustUseAttribute) { + node.removeAttribute(propertyInfo.attributeName); + } else { + var propName = propertyInfo.propertyName; + var defaultValue = DOMProperty.getDefaultValueForProperty(node.nodeName, propName); + if (!propertyInfo.hasSideEffects || '' + node[propName] !== defaultValue) { + node[propName] = defaultValue; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + node.removeAttribute(name); + } else if (process.env.NODE_ENV !== 'production') { + warnUnknownProperty(name); + } + } + + }; + + ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', { + setValueForProperty: 'setValueForProperty', + setValueForAttribute: 'setValueForAttribute', + deleteValueForProperty: 'deleteValueForProperty' + }); + + module.exports = DOMPropertyOperations; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 24 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMProperty + * @typechecks static-only + */ + + 'use strict'; + + var invariant = __webpack_require__(14); + + function checkMask(value, bitmask) { + return (value & bitmask) === bitmask; + } + + var DOMPropertyInjection = { + /** + * Mapping from normalized, camelcased property names to a configuration that + * specifies how the associated DOM property should be accessed or rendered. + */ + MUST_USE_ATTRIBUTE: 0x1, + MUST_USE_PROPERTY: 0x2, + HAS_SIDE_EFFECTS: 0x4, + HAS_BOOLEAN_VALUE: 0x8, + HAS_NUMERIC_VALUE: 0x10, + HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, + HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, + + /** + * Inject some specialized knowledge about the DOM. This takes a config object + * with the following properties: + * + * isCustomAttribute: function that given an attribute name will return true + * if it can be inserted into the DOM verbatim. Useful for data-* or aria-* + * attributes where it's impossible to enumerate all of the possible + * attribute names, + * + * Properties: object mapping DOM property name to one of the + * DOMPropertyInjection constants or null. If your attribute isn't in here, + * it won't get written to the DOM. + * + * DOMAttributeNames: object mapping React attribute name to the DOM + * attribute name. Attribute names not specified use the **lowercase** + * normalized name. + * + * DOMAttributeNamespaces: object mapping React attribute name to the DOM + * attribute namespace URL. (Attribute names not specified use no namespace.) + * + * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. + * Property names not specified use the normalized name. + * + * DOMMutationMethods: Properties that require special mutation methods. If + * `value` is undefined, the mutation method should unset the property. + * + * @param {object} domPropertyConfig the config as described above. + */ + injectDOMPropertyConfig: function (domPropertyConfig) { + var Injection = DOMPropertyInjection; + var Properties = domPropertyConfig.Properties || {}; + var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {}; + var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; + var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; + var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; + + if (domPropertyConfig.isCustomAttribute) { + DOMProperty._isCustomAttributeFunctions.push(domPropertyConfig.isCustomAttribute); + } + + for (var propName in Properties) { + !!DOMProperty.properties.hasOwnProperty(propName) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + '\'%s\' which has already been injected. You may be accidentally ' + 'injecting the same DOM property config twice, or you may be ' + 'injecting two configs that have conflicting property names.', propName) : invariant(false) : undefined; + + var lowerCased = propName.toLowerCase(); + var propConfig = Properties[propName]; + + var propertyInfo = { + attributeName: lowerCased, + attributeNamespace: null, + propertyName: propName, + mutationMethod: null, + + mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE), + mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY), + hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS), + hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE), + hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE), + hasPositiveNumericValue: checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE), + hasOverloadedBooleanValue: checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE) + }; + + !(!propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Cannot require using both attribute and property: %s', propName) : invariant(false) : undefined; + !(propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Properties that have side effects must use property: %s', propName) : invariant(false) : undefined; + !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + 'numeric value, but not a combination: %s', propName) : invariant(false) : undefined; + + if (process.env.NODE_ENV !== 'production') { + DOMProperty.getPossibleStandardName[lowerCased] = propName; + } + + if (DOMAttributeNames.hasOwnProperty(propName)) { + var attributeName = DOMAttributeNames[propName]; + propertyInfo.attributeName = attributeName; + if (process.env.NODE_ENV !== 'production') { + DOMProperty.getPossibleStandardName[attributeName] = propName; + } + } + + if (DOMAttributeNamespaces.hasOwnProperty(propName)) { + propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName]; + } + + if (DOMPropertyNames.hasOwnProperty(propName)) { + propertyInfo.propertyName = DOMPropertyNames[propName]; + } + + if (DOMMutationMethods.hasOwnProperty(propName)) { + propertyInfo.mutationMethod = DOMMutationMethods[propName]; + } + + DOMProperty.properties[propName] = propertyInfo; + } + } + }; + var defaultValueCache = {}; + + /** + * DOMProperty exports lookup objects that can be used like functions: + * + * > DOMProperty.isValid['id'] + * true + * > DOMProperty.isValid['foobar'] + * undefined + * + * Although this may be confusing, it performs better in general. + * + * @see http://jsperf.com/key-exists + * @see http://jsperf.com/key-missing + */ + var DOMProperty = { + + ID_ATTRIBUTE_NAME: 'data-reactid', + + /** + * Map from property "standard name" to an object with info about how to set + * the property in the DOM. Each object contains: + * + * attributeName: + * Used when rendering markup or with `*Attribute()`. + * attributeNamespace + * propertyName: + * Used on DOM node instances. (This includes properties that mutate due to + * external factors.) + * mutationMethod: + * If non-null, used instead of the property or `setAttribute()` after + * initial render. + * mustUseAttribute: + * Whether the property must be accessed and mutated using `*Attribute()`. + * (This includes anything that fails ` in `.) + * mustUseProperty: + * Whether the property must be accessed and mutated as an object property. + * hasSideEffects: + * Whether or not setting a value causes side effects such as triggering + * resources to be loaded or text selection changes. If true, we read from + * the DOM before updating to ensure that the value is only set if it has + * changed. + * hasBooleanValue: + * Whether the property should be removed when set to a falsey value. + * hasNumericValue: + * Whether the property must be numeric or parse as a numeric and should be + * removed when set to a falsey value. + * hasPositiveNumericValue: + * Whether the property must be positive numeric or parse as a positive + * numeric and should be removed when set to a falsey value. + * hasOverloadedBooleanValue: + * Whether the property can be used as a flag as well as with a value. + * Removed when strictly equal to false; present without a value when + * strictly equal to true; present with a value otherwise. + */ + properties: {}, + + /** + * Mapping from lowercase property names to the properly cased version, used + * to warn in the case of missing properties. Available only in __DEV__. + * @type {Object} + */ + getPossibleStandardName: process.env.NODE_ENV !== 'production' ? {} : null, + + /** + * All of the isCustomAttribute() functions that have been injected. + */ + _isCustomAttributeFunctions: [], + + /** + * Checks whether a property name is a custom attribute. + * @method + */ + isCustomAttribute: function (attributeName) { + for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { + var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; + if (isCustomAttributeFn(attributeName)) { + return true; + } + } + return false; + }, + + /** + * Returns the default property value for a DOM property (i.e., not an + * attribute). Most default values are '' or false, but not all. Worse yet, + * some (in particular, `type`) vary depending on the type of element. + * + * TODO: Is it better to grab all the possible properties when creating an + * element to avoid having to create the same element twice? + */ + getDefaultValueForProperty: function (nodeName, prop) { + var nodeDefaults = defaultValueCache[nodeName]; + var testElement; + if (!nodeDefaults) { + defaultValueCache[nodeName] = nodeDefaults = {}; + } + if (!(prop in nodeDefaults)) { + testElement = document.createElement(nodeName); + nodeDefaults[prop] = testElement[prop]; + } + return nodeDefaults[prop]; + }, + + injection: DOMPropertyInjection + }; + + module.exports = DOMProperty; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 25 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule quoteAttributeValueForBrowser + */ + + 'use strict'; + + var escapeTextContentForBrowser = __webpack_require__(22); + + /** + * Escapes attribute value to prevent scripting attacks. + * + * @param {*} value Value to escape. + * @return {string} An escaped string. + */ + function quoteAttributeValueForBrowser(value) { + return '"' + escapeTextContentForBrowser(value) + '"'; + } + + module.exports = quoteAttributeValueForBrowser; + +/***/ }, +/* 26 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2014-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule warning + */ + + 'use strict'; + + var emptyFunction = __webpack_require__(16); + + /** + * Similar to invariant but only logs a warning if the condition is not met. + * This can be used to log issues in development environments in critical + * paths. Removing the logging code for production environments will keep the + * same logic and follow the same code paths. + */ + + var warning = emptyFunction; + + if (process.env.NODE_ENV !== 'production') { + warning = function (condition, format) { + for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + args[_key - 2] = arguments[_key]; + } + + if (format === undefined) { + throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument'); + } + + if (format.indexOf('Failed Composite propType: ') === 0) { + return; // Ignore CompositeComponent proptype check. + } + + if (!condition) { + var argIndex = 0; + var message = 'Warning: ' + format.replace(/%s/g, function () { + return args[argIndex++]; + }); + if (typeof console !== 'undefined') { + console.error(message); + } + try { + // --- Welcome to debugging React --- + // This error was thrown as a convenience so that you can use this stack + // to find the callsite that caused this warning to fire. + throw new Error(message); + } catch (x) {} + } + }; + } + + module.exports = warning; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 27 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentBrowserEnvironment + */ + + 'use strict'; + + var ReactDOMIDOperations = __webpack_require__(28); + var ReactMount = __webpack_require__(29); + + /** + * Abstracts away all functionality of the reconciler that requires knowledge of + * the browser context. TODO: These callers should be refactored to avoid the + * need for this injection. + */ + var ReactComponentBrowserEnvironment = { + + processChildrenUpdates: ReactDOMIDOperations.dangerouslyProcessChildrenUpdates, + + replaceNodeWithMarkupByID: ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + + /** + * If a particular environment requires that some resources be cleaned up, + * specify this in the injected Mixin. In the DOM, we would likely want to + * purge any cached node ID lookups. + * + * @private + */ + unmountIDFromEnvironment: function (rootNodeID) { + ReactMount.purgeID(rootNodeID); + } + + }; + + module.exports = ReactComponentBrowserEnvironment; + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMIDOperations + * @typechecks static-only + */ + + 'use strict'; + + var DOMChildrenOperations = __webpack_require__(8); + var DOMPropertyOperations = __webpack_require__(23); + var ReactMount = __webpack_require__(29); + var ReactPerf = __webpack_require__(19); + + var invariant = __webpack_require__(14); + + /** + * Errors for properties that should not be updated with `updatePropertyByID()`. + * + * @type {object} + * @private + */ + var INVALID_PROPERTY_ERRORS = { + dangerouslySetInnerHTML: '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.', + style: '`style` must be set using `updateStylesByID()`.' + }; + + /** + * Operations used to process updates to DOM nodes. + */ + var ReactDOMIDOperations = { + + /** + * Updates a DOM node with new property values. This should only be used to + * update DOM properties in `DOMProperty`. + * + * @param {string} id ID of the node to update. + * @param {string} name A valid property name, see `DOMProperty`. + * @param {*} value New value of the property. + * @internal + */ + updatePropertyByID: function (id, name, value) { + var node = ReactMount.getNode(id); + !!INVALID_PROPERTY_ERRORS.hasOwnProperty(name) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'updatePropertyByID(...): %s', INVALID_PROPERTY_ERRORS[name]) : invariant(false) : undefined; + + // If we're updating to null or undefined, we should remove the property + // from the DOM node instead of inadvertantly setting to a string. This + // brings us in line with the same behavior we have on initial render. + if (value != null) { + DOMPropertyOperations.setValueForProperty(node, name, value); + } else { + DOMPropertyOperations.deleteValueForProperty(node, name); + } + }, + + /** + * Replaces a DOM node that exists in the document with markup. + * + * @param {string} id ID of child to be replaced. + * @param {string} markup Dangerous markup to inject in place of child. + * @internal + * @see {Danger.dangerouslyReplaceNodeWithMarkup} + */ + dangerouslyReplaceNodeWithMarkupByID: function (id, markup) { + var node = ReactMount.getNode(id); + DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup); + }, + + /** + * Updates a component's children by processing a series of updates. + * + * @param {array} updates List of update configurations. + * @param {array} markup List of markup strings. + * @internal + */ + dangerouslyProcessChildrenUpdates: function (updates, markup) { + for (var i = 0; i < updates.length; i++) { + updates[i].parentNode = ReactMount.getNode(updates[i].parentID); + } + DOMChildrenOperations.processUpdates(updates, markup); + } + }; + + ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', { + dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID', + dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates' + }); + + module.exports = ReactDOMIDOperations; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 29 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactMount + */ + + 'use strict'; + + var DOMProperty = __webpack_require__(24); + var ReactBrowserEventEmitter = __webpack_require__(30); + var ReactCurrentOwner = __webpack_require__(6); + var ReactDOMFeatureFlags = __webpack_require__(42); + var ReactElement = __webpack_require__(43); + var ReactEmptyComponentRegistry = __webpack_require__(45); + var ReactInstanceHandles = __webpack_require__(46); + var ReactInstanceMap = __webpack_require__(48); + var ReactMarkupChecksum = __webpack_require__(49); + var ReactPerf = __webpack_require__(19); + var ReactReconciler = __webpack_require__(51); + var ReactUpdateQueue = __webpack_require__(54); + var ReactUpdates = __webpack_require__(55); + + var assign = __webpack_require__(40); + var emptyObject = __webpack_require__(59); + var containsNode = __webpack_require__(60); + var instantiateReactComponent = __webpack_require__(63); + var invariant = __webpack_require__(14); + var setInnerHTML = __webpack_require__(20); + var shouldUpdateReactComponent = __webpack_require__(68); + var validateDOMNesting = __webpack_require__(71); + var warning = __webpack_require__(26); + + var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; + var nodeCache = {}; + + var ELEMENT_NODE_TYPE = 1; + var DOC_NODE_TYPE = 9; + var DOCUMENT_FRAGMENT_NODE_TYPE = 11; + + var ownerDocumentContextKey = '__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2); + + /** Mapping from reactRootID to React component instance. */ + var instancesByReactRootID = {}; + + /** Mapping from reactRootID to `container` nodes. */ + var containersByReactRootID = {}; + + if (process.env.NODE_ENV !== 'production') { + /** __DEV__-only mapping from reactRootID to root elements. */ + var rootElementsByReactRootID = {}; + } + + // Used to store breadth-first search state in findComponentRoot. + var findComponentRootReusableArray = []; + + /** + * Finds the index of the first character + * that's not common between the two given strings. + * + * @return {number} the index of the character where the strings diverge + */ + function firstDifferenceIndex(string1, string2) { + var minLen = Math.min(string1.length, string2.length); + for (var i = 0; i < minLen; i++) { + if (string1.charAt(i) !== string2.charAt(i)) { + return i; + } + } + return string1.length === string2.length ? -1 : minLen; + } + + /** + * @param {DOMElement|DOMDocument} container DOM element that may contain + * a React component + * @return {?*} DOM element that may have the reactRoot ID, or null. + */ + function getReactRootElementInContainer(container) { + if (!container) { + return null; + } + + if (container.nodeType === DOC_NODE_TYPE) { + return container.documentElement; + } else { + return container.firstChild; + } + } + + /** + * @param {DOMElement} container DOM element that may contain a React component. + * @return {?string} A "reactRoot" ID, if a React component is rendered. + */ + function getReactRootID(container) { + var rootElement = getReactRootElementInContainer(container); + return rootElement && ReactMount.getID(rootElement); + } + + /** + * Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form + * element can return its control whose name or ID equals ATTR_NAME. All + * DOM nodes support `getAttributeNode` but this can also get called on + * other objects so just return '' if we're given something other than a + * DOM node (such as window). + * + * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node. + * @return {string} ID of the supplied `domNode`. + */ + function getID(node) { + var id = internalGetID(node); + if (id) { + if (nodeCache.hasOwnProperty(id)) { + var cached = nodeCache[id]; + if (cached !== node) { + !!isValid(cached, id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactMount: Two valid but unequal nodes with the same `%s`: %s', ATTR_NAME, id) : invariant(false) : undefined; + + nodeCache[id] = node; + } + } else { + nodeCache[id] = node; + } + } + + return id; + } + + function internalGetID(node) { + // If node is something like a window, document, or text node, none of + // which support attributes or a .getAttribute method, gracefully return + // the empty string, as if the attribute were missing. + return node && node.getAttribute && node.getAttribute(ATTR_NAME) || ''; + } + + /** + * Sets the React-specific ID of the given node. + * + * @param {DOMElement} node The DOM node whose ID will be set. + * @param {string} id The value of the ID attribute. + */ + function setID(node, id) { + var oldID = internalGetID(node); + if (oldID !== id) { + delete nodeCache[oldID]; + } + node.setAttribute(ATTR_NAME, id); + nodeCache[id] = node; + } + + /** + * Finds the node with the supplied React-generated DOM ID. + * + * @param {string} id A React-generated DOM ID. + * @return {DOMElement} DOM node with the suppled `id`. + * @internal + */ + function getNode(id) { + if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { + nodeCache[id] = ReactMount.findReactNodeByID(id); + } + return nodeCache[id]; + } + + /** + * Finds the node with the supplied public React instance. + * + * @param {*} instance A public React instance. + * @return {?DOMElement} DOM node with the suppled `id`. + * @internal + */ + function getNodeFromInstance(instance) { + var id = ReactInstanceMap.get(instance)._rootNodeID; + if (ReactEmptyComponentRegistry.isNullComponentID(id)) { + return null; + } + if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { + nodeCache[id] = ReactMount.findReactNodeByID(id); + } + return nodeCache[id]; + } + + /** + * A node is "valid" if it is contained by a currently mounted container. + * + * This means that the node does not have to be contained by a document in + * order to be considered valid. + * + * @param {?DOMElement} node The candidate DOM node. + * @param {string} id The expected ID of the node. + * @return {boolean} Whether the node is contained by a mounted container. + */ + function isValid(node, id) { + if (node) { + !(internalGetID(node) === id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactMount: Unexpected modification of `%s`', ATTR_NAME) : invariant(false) : undefined; + + var container = ReactMount.findReactContainerForID(id); + if (container && containsNode(container, node)) { + return true; + } + } + + return false; + } + + /** + * Causes the cache to forget about one React-specific ID. + * + * @param {string} id The ID to forget. + */ + function purgeID(id) { + delete nodeCache[id]; + } + + var deepestNodeSoFar = null; + function findDeepestCachedAncestorImpl(ancestorID) { + var ancestor = nodeCache[ancestorID]; + if (ancestor && isValid(ancestor, ancestorID)) { + deepestNodeSoFar = ancestor; + } else { + // This node isn't populated in the cache, so presumably none of its + // descendants are. Break out of the loop. + return false; + } + } + + /** + * Return the deepest cached node whose ID is a prefix of `targetID`. + */ + function findDeepestCachedAncestor(targetID) { + deepestNodeSoFar = null; + ReactInstanceHandles.traverseAncestors(targetID, findDeepestCachedAncestorImpl); + + var foundNode = deepestNodeSoFar; + deepestNodeSoFar = null; + return foundNode; + } + + /** + * Mounts this component and inserts it into the DOM. + * + * @param {ReactComponent} componentInstance The instance to mount. + * @param {string} rootID DOM ID of the root node. + * @param {DOMElement} container DOM element to mount into. + * @param {ReactReconcileTransaction} transaction + * @param {boolean} shouldReuseMarkup If true, do not insert markup + */ + function mountComponentIntoNode(componentInstance, rootID, container, transaction, shouldReuseMarkup, context) { + if (ReactDOMFeatureFlags.useCreateElement) { + context = assign({}, context); + if (container.nodeType === DOC_NODE_TYPE) { + context[ownerDocumentContextKey] = container; + } else { + context[ownerDocumentContextKey] = container.ownerDocument; + } + } + if (process.env.NODE_ENV !== 'production') { + if (context === emptyObject) { + context = {}; + } + var tag = container.nodeName.toLowerCase(); + context[validateDOMNesting.ancestorInfoContextKey] = validateDOMNesting.updatedAncestorInfo(null, tag, null); + } + var markup = ReactReconciler.mountComponent(componentInstance, rootID, transaction, context); + componentInstance._renderedComponent._topLevelWrapper = componentInstance; + ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup, transaction); + } + + /** + * Batched mount. + * + * @param {ReactComponent} componentInstance The instance to mount. + * @param {string} rootID DOM ID of the root node. + * @param {DOMElement} container DOM element to mount into. + * @param {boolean} shouldReuseMarkup If true, do not insert markup + */ + function batchedMountComponentIntoNode(componentInstance, rootID, container, shouldReuseMarkup, context) { + var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( + /* forceHTML */shouldReuseMarkup); + transaction.perform(mountComponentIntoNode, null, componentInstance, rootID, container, transaction, shouldReuseMarkup, context); + ReactUpdates.ReactReconcileTransaction.release(transaction); + } + + /** + * Unmounts a component and removes it from the DOM. + * + * @param {ReactComponent} instance React component instance. + * @param {DOMElement} container DOM element to unmount from. + * @final + * @internal + * @see {ReactMount.unmountComponentAtNode} + */ + function unmountComponentFromNode(instance, container) { + ReactReconciler.unmountComponent(instance); + + if (container.nodeType === DOC_NODE_TYPE) { + container = container.documentElement; + } + + // http://jsperf.com/emptying-a-node + while (container.lastChild) { + container.removeChild(container.lastChild); + } + } + + /** + * True if the supplied DOM node has a direct React-rendered child that is + * not a React root element. Useful for warning in `render`, + * `unmountComponentAtNode`, etc. + * + * @param {?DOMElement} node The candidate DOM node. + * @return {boolean} True if the DOM element contains a direct child that was + * rendered by React but is not a root element. + * @internal + */ + function hasNonRootReactChild(node) { + var reactRootID = getReactRootID(node); + return reactRootID ? reactRootID !== ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false; + } + + /** + * Returns the first (deepest) ancestor of a node which is rendered by this copy + * of React. + */ + function findFirstReactDOMImpl(node) { + // This node might be from another React instance, so we make sure not to + // examine the node cache here + for (; node && node.parentNode !== node; node = node.parentNode) { + if (node.nodeType !== 1) { + // Not a DOMElement, therefore not a React component + continue; + } + var nodeID = internalGetID(node); + if (!nodeID) { + continue; + } + var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID); + + // If containersByReactRootID contains the container we find by crawling up + // the tree, we know that this instance of React rendered the node. + // nb. isValid's strategy (with containsNode) does not work because render + // trees may be nested and we don't want a false positive in that case. + var current = node; + var lastID; + do { + lastID = internalGetID(current); + current = current.parentNode; + if (current == null) { + // The passed-in node has been detached from the container it was + // originally rendered into. + return null; + } + } while (lastID !== reactRootID); + + if (current === containersByReactRootID[reactRootID]) { + return node; + } + } + return null; + } + + /** + * Temporary (?) hack so that we can store all top-level pending updates on + * composites instead of having to worry about different types of components + * here. + */ + var TopLevelWrapper = function () {}; + TopLevelWrapper.prototype.isReactComponent = {}; + if (process.env.NODE_ENV !== 'production') { + TopLevelWrapper.displayName = 'TopLevelWrapper'; + } + TopLevelWrapper.prototype.render = function () { + // this.props is actually a ReactElement + return this.props; + }; + + /** + * Mounting is the process of initializing a React component by creating its + * representative DOM elements and inserting them into a supplied `container`. + * Any prior content inside `container` is destroyed in the process. + * + * ReactMount.render( + * component, + * document.getElementById('container') + * ); + * + *
<-- Supplied `container`. + *
<-- Rendered reactRoot of React + * // ... component. + *
+ *
+ * + * Inside of `container`, the first element rendered is the "reactRoot". + */ + var ReactMount = { + + TopLevelWrapper: TopLevelWrapper, + + /** Exposed for debugging purposes **/ + _instancesByReactRootID: instancesByReactRootID, + + /** + * This is a hook provided to support rendering React components while + * ensuring that the apparent scroll position of its `container` does not + * change. + * + * @param {DOMElement} container The `container` being rendered into. + * @param {function} renderCallback This must be called once to do the render. + */ + scrollMonitor: function (container, renderCallback) { + renderCallback(); + }, + + /** + * Take a component that's already mounted into the DOM and replace its props + * @param {ReactComponent} prevComponent component instance already in the DOM + * @param {ReactElement} nextElement component instance to render + * @param {DOMElement} container container to render into + * @param {?function} callback function triggered on completion + */ + _updateRootComponent: function (prevComponent, nextElement, container, callback) { + ReactMount.scrollMonitor(container, function () { + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); + if (callback) { + ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); + } + }); + + if (process.env.NODE_ENV !== 'production') { + // Record the root element in case it later gets transplanted. + rootElementsByReactRootID[getReactRootID(container)] = getReactRootElementInContainer(container); + } + + return prevComponent; + }, + + /** + * Register a component into the instance map and starts scroll value + * monitoring + * @param {ReactComponent} nextComponent component instance to render + * @param {DOMElement} container container to render into + * @return {string} reactRoot ID prefix + */ + _registerComponent: function (nextComponent, container) { + !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : invariant(false) : undefined; + + ReactBrowserEventEmitter.ensureScrollValueMonitoring(); + + var reactRootID = ReactMount.registerContainer(container); + instancesByReactRootID[reactRootID] = nextComponent; + return reactRootID; + }, + + /** + * Render a new component into the DOM. + * @param {ReactElement} nextElement element to render + * @param {DOMElement} container container to render into + * @param {boolean} shouldReuseMarkup if we should skip the markup insertion + * @return {ReactComponent} nextComponent + */ + _renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) { + // Various parts of our code (such as ReactCompositeComponent's + // _renderValidatedComponent) assume that calls to render aren't nested; + // verify that that's the case. + process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : undefined; + + var componentInstance = instantiateReactComponent(nextElement, null); + var reactRootID = ReactMount._registerComponent(componentInstance, container); + + // The initial render is synchronous but any updates that happen during + // rendering, in componentWillMount or componentDidMount, will be batched + // according to the current batching strategy. + + ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, reactRootID, container, shouldReuseMarkup, context); + + if (process.env.NODE_ENV !== 'production') { + // Record the root element in case it later gets transplanted. + rootElementsByReactRootID[reactRootID] = getReactRootElementInContainer(container); + } + + return componentInstance; + }, + + /** + * Renders a React component into the DOM in the supplied `container`. + * + * If the React component was previously rendered into `container`, this will + * perform an update on it and only mutate the DOM as necessary to reflect the + * latest React component. + * + * @param {ReactComponent} parentComponent The conceptual parent of this render tree. + * @param {ReactElement} nextElement Component element to render. + * @param {DOMElement} container DOM element to render into. + * @param {?function} callback function triggered on completion + * @return {ReactComponent} Component instance rendered in `container`. + */ + renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) { + !(parentComponent != null && parentComponent._reactInternalInstance != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'parentComponent must be a valid React Component') : invariant(false) : undefined; + return ReactMount._renderSubtreeIntoContainer(parentComponent, nextElement, container, callback); + }, + + _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) { + !ReactElement.isValidElement(nextElement) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactDOM.render(): Invalid component element.%s', typeof nextElement === 'string' ? ' Instead of passing an element string, make sure to instantiate ' + 'it by passing it to React.createElement.' : typeof nextElement === 'function' ? ' Instead of passing a component class, make sure to instantiate ' + 'it by passing it to React.createElement.' : + // Check if it quacks like an element + nextElement != null && nextElement.props !== undefined ? ' This may be caused by unintentionally loading two independent ' + 'copies of React.' : '') : invariant(false) : undefined; + + process.env.NODE_ENV !== 'production' ? warning(!container || !container.tagName || container.tagName.toUpperCase() !== 'BODY', 'render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.') : undefined; + + var nextWrappedElement = new ReactElement(TopLevelWrapper, null, null, null, null, null, nextElement); + + var prevComponent = instancesByReactRootID[getReactRootID(container)]; + + if (prevComponent) { + var prevWrappedElement = prevComponent._currentElement; + var prevElement = prevWrappedElement.props; + if (shouldUpdateReactComponent(prevElement, nextElement)) { + var publicInst = prevComponent._renderedComponent.getPublicInstance(); + var updatedCallback = callback && function () { + callback.call(publicInst); + }; + ReactMount._updateRootComponent(prevComponent, nextWrappedElement, container, updatedCallback); + return publicInst; + } else { + ReactMount.unmountComponentAtNode(container); + } + } + + var reactRootElement = getReactRootElementInContainer(container); + var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); + var containerHasNonRootReactChild = hasNonRootReactChild(container); + + if (process.env.NODE_ENV !== 'production') { + process.env.NODE_ENV !== 'production' ? warning(!containerHasNonRootReactChild, 'render(...): Replacing React-rendered children with a new root ' + 'component. If you intended to update the children of this node, ' + 'you should instead have the existing children update their state ' + 'and render the new components instead of calling ReactDOM.render.') : undefined; + + if (!containerHasReactMarkup || reactRootElement.nextSibling) { + var rootElementSibling = reactRootElement; + while (rootElementSibling) { + if (internalGetID(rootElementSibling)) { + process.env.NODE_ENV !== 'production' ? warning(false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.') : undefined; + break; + } + rootElementSibling = rootElementSibling.nextSibling; + } + } + } + + var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; + var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, parentComponent != null ? parentComponent._reactInternalInstance._processChildContext(parentComponent._reactInternalInstance._context) : emptyObject)._renderedComponent.getPublicInstance(); + if (callback) { + callback.call(component); + } + return component; + }, + + /** + * Renders a React component into the DOM in the supplied `container`. + * + * If the React component was previously rendered into `container`, this will + * perform an update on it and only mutate the DOM as necessary to reflect the + * latest React component. + * + * @param {ReactElement} nextElement Component element to render. + * @param {DOMElement} container DOM element to render into. + * @param {?function} callback function triggered on completion + * @return {ReactComponent} Component instance rendered in `container`. + */ + render: function (nextElement, container, callback) { + return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback); + }, + + /** + * Registers a container node into which React components will be rendered. + * This also creates the "reactRoot" ID that will be assigned to the element + * rendered within. + * + * @param {DOMElement} container DOM element to register as a container. + * @return {string} The "reactRoot" ID of elements rendered within. + */ + registerContainer: function (container) { + var reactRootID = getReactRootID(container); + if (reactRootID) { + // If one exists, make sure it is a valid "reactRoot" ID. + reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID); + } + if (!reactRootID) { + // No valid "reactRoot" ID found, create one. + reactRootID = ReactInstanceHandles.createReactRootID(); + } + containersByReactRootID[reactRootID] = container; + return reactRootID; + }, + + /** + * Unmounts and destroys the React component rendered in the `container`. + * + * @param {DOMElement} container DOM element containing a React component. + * @return {boolean} True if a component was found in and unmounted from + * `container` + */ + unmountComponentAtNode: function (container) { + // Various parts of our code (such as ReactCompositeComponent's + // _renderValidatedComponent) assume that calls to render aren't nested; + // verify that that's the case. (Strictly speaking, unmounting won't cause a + // render but we still don't expect to be in a render call here.) + process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, 'unmountComponentAtNode(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from render ' + 'is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : undefined; + + !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'unmountComponentAtNode(...): Target container is not a DOM element.') : invariant(false) : undefined; + + var reactRootID = getReactRootID(container); + var component = instancesByReactRootID[reactRootID]; + if (!component) { + // Check if the node being unmounted was rendered by React, but isn't a + // root node. + var containerHasNonRootReactChild = hasNonRootReactChild(container); + + // Check if the container itself is a React root node. + var containerID = internalGetID(container); + var isContainerReactRoot = containerID && containerID === ReactInstanceHandles.getReactRootIDFromNodeID(containerID); + + if (process.env.NODE_ENV !== 'production') { + process.env.NODE_ENV !== 'production' ? warning(!containerHasNonRootReactChild, 'unmountComponentAtNode(): The node you\'re attempting to unmount ' + 'was rendered by React and is not a top-level container. %s', isContainerReactRoot ? 'You may have accidentally passed in a React root node instead ' + 'of its container.' : 'Instead, have the parent component update its state and ' + 'rerender in order to remove this component.') : undefined; + } + + return false; + } + ReactUpdates.batchedUpdates(unmountComponentFromNode, component, container); + delete instancesByReactRootID[reactRootID]; + delete containersByReactRootID[reactRootID]; + if (process.env.NODE_ENV !== 'production') { + delete rootElementsByReactRootID[reactRootID]; + } + return true; + }, + + /** + * Finds the container DOM element that contains React component to which the + * supplied DOM `id` belongs. + * + * @param {string} id The ID of an element rendered by a React component. + * @return {?DOMElement} DOM element that contains the `id`. + */ + findReactContainerForID: function (id) { + var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id); + var container = containersByReactRootID[reactRootID]; + + if (process.env.NODE_ENV !== 'production') { + var rootElement = rootElementsByReactRootID[reactRootID]; + if (rootElement && rootElement.parentNode !== container) { + process.env.NODE_ENV !== 'production' ? warning( + // Call internalGetID here because getID calls isValid which calls + // findReactContainerForID (this function). + internalGetID(rootElement) === reactRootID, 'ReactMount: Root element ID differed from reactRootID.') : undefined; + var containerChild = container.firstChild; + if (containerChild && reactRootID === internalGetID(containerChild)) { + // If the container has a new child with the same ID as the old + // root element, then rootElementsByReactRootID[reactRootID] is + // just stale and needs to be updated. The case that deserves a + // warning is when the container is empty. + rootElementsByReactRootID[reactRootID] = containerChild; + } else { + process.env.NODE_ENV !== 'production' ? warning(false, 'ReactMount: Root element has been removed from its original ' + 'container. New container: %s', rootElement.parentNode) : undefined; + } + } + } + + return container; + }, + + /** + * Finds an element rendered by React with the supplied ID. + * + * @param {string} id ID of a DOM node in the React component. + * @return {DOMElement} Root DOM node of the React component. + */ + findReactNodeByID: function (id) { + var reactRoot = ReactMount.findReactContainerForID(id); + return ReactMount.findComponentRoot(reactRoot, id); + }, + + /** + * Traverses up the ancestors of the supplied node to find a node that is a + * DOM representation of a React component rendered by this copy of React. + * + * @param {*} node + * @return {?DOMEventTarget} + * @internal + */ + getFirstReactDOM: function (node) { + return findFirstReactDOMImpl(node); + }, + + /** + * Finds a node with the supplied `targetID` inside of the supplied + * `ancestorNode`. Exploits the ID naming scheme to perform the search + * quickly. + * + * @param {DOMEventTarget} ancestorNode Search from this root. + * @pararm {string} targetID ID of the DOM representation of the component. + * @return {DOMEventTarget} DOM node with the supplied `targetID`. + * @internal + */ + findComponentRoot: function (ancestorNode, targetID) { + var firstChildren = findComponentRootReusableArray; + var childIndex = 0; + + var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode; + + if (process.env.NODE_ENV !== 'production') { + // This will throw on the next line; give an early warning + process.env.NODE_ENV !== 'production' ? warning(deepestAncestor != null, 'React can\'t find the root component node for data-reactid value ' + '`%s`. If you\'re seeing this message, it probably means that ' + 'you\'ve loaded two copies of React on the page. At this time, only ' + 'a single copy of React can be loaded at a time.', targetID) : undefined; + } + + firstChildren[0] = deepestAncestor.firstChild; + firstChildren.length = 1; + + while (childIndex < firstChildren.length) { + var child = firstChildren[childIndex++]; + var targetChild; + + while (child) { + var childID = ReactMount.getID(child); + if (childID) { + // Even if we find the node we're looking for, we finish looping + // through its siblings to ensure they're cached so that we don't have + // to revisit this node again. Otherwise, we make n^2 calls to getID + // when visiting the many children of a single node in order. + + if (targetID === childID) { + targetChild = child; + } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) { + // If we find a child whose ID is an ancestor of the given ID, + // then we can be sure that we only want to search the subtree + // rooted at this child, so we can throw out the rest of the + // search state. + firstChildren.length = childIndex = 0; + firstChildren.push(child.firstChild); + } + } else { + // If this child had no ID, then there's a chance that it was + // injected automatically by the browser, as when a `` + // element sprouts an extra `` child as a side effect of + // `.innerHTML` parsing. Optimistically continue down this + // branch, but not before examining the other siblings. + firstChildren.push(child.firstChild); + } + + child = child.nextSibling; + } + + if (targetChild) { + // Emptying firstChildren/findComponentRootReusableArray is + // not necessary for correctness, but it helps the GC reclaim + // any nodes that were left at the end of the search. + firstChildren.length = 0; + + return targetChild; + } + } + + firstChildren.length = 0; + + true ? process.env.NODE_ENV !== 'production' ? invariant(false, 'findComponentRoot(..., %s): Unable to find element. This probably ' + 'means the DOM was unexpectedly mutated (e.g., by the browser), ' + 'usually due to forgetting a when using tables, nesting tags ' + 'like ,

, or , or using non-SVG elements in an ' + 'parent. ' + 'Try inspecting the child nodes of the element with React ID `%s`.', targetID, ReactMount.getID(ancestorNode)) : invariant(false) : undefined; + }, + + _mountImageIntoNode: function (markup, container, shouldReuseMarkup, transaction) { + !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'mountComponentIntoNode(...): Target container is not valid.') : invariant(false) : undefined; + + if (shouldReuseMarkup) { + var rootElement = getReactRootElementInContainer(container); + if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { + return; + } else { + var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); + rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); + + var rootMarkup = rootElement.outerHTML; + rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum); + + var normalizedMarkup = markup; + if (process.env.NODE_ENV !== 'production') { + // because rootMarkup is retrieved from the DOM, various normalizations + // will have occurred which will not be present in `markup`. Here, + // insert markup into a

or