diff --git a/Gruntfile.js b/Gruntfile.js
index a77384423c..21af4726c4 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -276,7 +276,6 @@ module.exports = function(grunt) {
 					'build/wp-includes/js/mce-view.js': ['./src/js/_enqueues/wp/mce-view.js'],
 					'build/wp-includes/js/media-editor.js': ['./src/js/_enqueues/wp/media/editor.js'],
 					'build/wp-includes/js/quicktags.js': ['./src/js/_enqueues/lib/quicktags.js'],
-					'build/wp-includes/js/shortcode.js': ['./src/js/_enqueues/wp/shortcode.js'],
 					'build/wp-includes/js/utils.js': ['./src/js/_enqueues/lib/cookies.js'],
 					'build/wp-includes/js/wp-a11y.js': ['./src/js/_enqueues/wp/a11y.js'],
 					'build/wp-includes/js/wp-ajax-response.js': ['./src/js/_enqueues/lib/ajax-response.js'],
@@ -815,7 +814,6 @@ module.exports = function(grunt) {
 					'src/wp-includes/js/mce-view.js': 'src/js/_enqueues/wp/mce-view.js',
 					'src/wp-includes/js/media-editor.js': 'src/js/_enqueues/wp/media/editor.js',
 					'src/wp-includes/js/quicktags.js': 'src/js/_enqueues/lib/quicktags.js',
-					'src/wp-includes/js/shortcode.js': 'src/js/_enqueues/wp/shortcode.js',
 					'src/wp-includes/js/utils.js': 'src/js/_enqueues/lib/cookies.js',
 					'src/wp-includes/js/wp-a11y.js': 'src/js/_enqueues/wp/a11y.js',
 					'src/wp-includes/js/wp-ajax-response.js': 'src/js/_enqueues/lib/ajax-response.js',
diff --git a/package-lock.json b/package-lock.json
index 3f81fc78a0..f8bf9667bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,39 @@
 	"lockfileVersion": 1,
 	"requires": true,
 	"dependencies": {
+		"@babel/polyfill": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.0.0.tgz",
+			"integrity": "sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q==",
+			"requires": {
+				"core-js": "^2.5.7",
+				"regenerator-runtime": "^0.11.1"
+			},
+			"dependencies": {
+				"regenerator-runtime": {
+					"version": "0.11.1",
+					"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+					"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+				}
+			}
+		},
+		"@babel/runtime": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
+			"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
+			"requires": {
+				"regenerator-runtime": "^0.12.0"
+			}
+		},
+		"@wordpress/shortcode": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-2.0.0.tgz",
+			"integrity": "sha512-XfBIkoX+vjZqDtE8lqOz3YKdX+/xppJryxriNoMaipqOpmcsuDfQxqNrrdr6ETL9CvQ/4gMmGC/oZ1BoqghjLQ==",
+			"requires": {
+				"@babel/runtime": "^7.0.0",
+				"lodash": "^4.17.10"
+			}
+		},
 		"abbrev": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -1801,7 +1834,7 @@
 		},
 		"compression": {
 			"version": "1.7.2",
-			"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz",
+			"resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz",
 			"integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=",
 			"dev": true,
 			"requires": {
@@ -1909,6 +1942,11 @@
 			"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
 			"dev": true
 		},
+		"core-js": {
+			"version": "2.5.7",
+			"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
+			"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
+		},
 		"core-util-is": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -6878,10 +6916,9 @@
 			}
 		},
 		"lodash": {
-			"version": "4.17.10",
-			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
-			"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
-			"dev": true
+			"version": "4.17.11",
+			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+			"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
 		},
 		"lodash._basecopy": {
 			"version": "3.0.1",
@@ -7245,6 +7282,11 @@
 				"mimic-fn": "^1.0.0"
 			}
 		},
+		"memize": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/memize/-/memize-1.0.5.tgz",
+			"integrity": "sha512-Dm8Jhb5kiC4+ynYsVR4QDXKt+o2dfqGuY4hE2x+XlXZkdndlT80bJxfcMv5QGp/FCy6MhG7f5ElpmKPFKOSEpg=="
+		},
 		"memory-fs": {
 			"version": "0.4.1",
 			"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -8151,7 +8193,7 @@
 		},
 		"onetime": {
 			"version": "1.1.0",
-			"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+			"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
 			"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
 			"dev": true
 		},
@@ -8882,6 +8924,11 @@
 				"strip-indent": "^1.0.1"
 			}
 		},
+		"regenerator-runtime": {
+			"version": "0.12.1",
+			"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
+			"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
+		},
 		"regex-cache": {
 			"version": "0.4.4",
 			"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
diff --git a/package.json b/package.json
index 06a5a5e343..caa92d21d6 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,8 @@
 		"webpack-dev-server": "^2.9.1"
 	},
 	"dependencies": {
+		"@babel/polyfill": "7.0.0",
+		"@wordpress/shortcode": "2.0.0",
 		"backbone": "1.3.3",
 		"imagesloaded": "3.2.0",
 		"jquery": "1.12.4",
@@ -52,7 +54,9 @@
 		"jquery-form": "4.2.1",
 		"jquery-hoverintent": "1.8.3",
 		"jquery-ui": "github:jquery/jquery-ui#1.11.4",
+		"lodash": "4.17.11",
 		"masonry-layout": "3.3.2",
+		"memize": "1.0.5",
 		"twemoji": "11.0.0",
 		"underscore": "1.8.3"
 	}
diff --git a/src/js/_enqueues/wp/shortcode.js b/src/js/_enqueues/wp/shortcode.js
index 8f02273ba7..6bb2aff311 100644
--- a/src/js/_enqueues/wp/shortcode.js
+++ b/src/js/_enqueues/wp/shortcode.js
@@ -1,362 +1,2 @@
-/**
- * Utility functions for parsing and handling shortcodes in JavaScript.
- *
- * @output wp-includes/js/shortcode.js
- */
-
-/**
- * Ensure the global `wp` object exists.
- *
- * @namespace wp
- */
 window.wp = window.wp || {};
-
-(function(){
-	wp.shortcode = {
-		// ### Find the next matching shortcode
-		//
-		// Given a shortcode `tag`, a block of `text`, and an optional starting
-		// `index`, returns the next matching shortcode or `undefined`.
-		//
-		// Shortcodes are formatted as an object that contains the match
-		// `content`, the matching `index`, and the parsed `shortcode` object.
-		next: function( tag, text, index ) {
-			var re = wp.shortcode.regexp( tag ),
-				match, result;
-
-			re.lastIndex = index || 0;
-			match = re.exec( text );
-
-			if ( ! match ) {
-				return;
-			}
-
-			// If we matched an escaped shortcode, try again.
-			if ( '[' === match[1] && ']' === match[7] ) {
-				return wp.shortcode.next( tag, text, re.lastIndex );
-			}
-
-			result = {
-				index:     match.index,
-				content:   match[0],
-				shortcode: wp.shortcode.fromMatch( match )
-			};
-
-			// If we matched a leading `[`, strip it from the match
-			// and increment the index accordingly.
-			if ( match[1] ) {
-				result.content = result.content.slice( 1 );
-				result.index++;
-			}
-
-			// If we matched a trailing `]`, strip it from the match.
-			if ( match[7] ) {
-				result.content = result.content.slice( 0, -1 );
-			}
-
-			return result;
-		},
-
-		// ### Replace matching shortcodes in a block of text
-		//
-		// Accepts a shortcode `tag`, content `text` to scan, and a `callback`
-		// to process the shortcode matches and return a replacement string.
-		// Returns the `text` with all shortcodes replaced.
-		//
-		// Shortcode matches are objects that contain the shortcode `tag`,
-		// a shortcode `attrs` object, the `content` between shortcode tags,
-		// and a boolean flag to indicate if the match was a `single` tag.
-		replace: function( tag, text, callback ) {
-			return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) {
-				// If both extra brackets exist, the shortcode has been
-				// properly escaped.
-				if ( left === '[' && right === ']' ) {
-					return match;
-				}
-
-				// Create the match object and pass it through the callback.
-				var result = callback( wp.shortcode.fromMatch( arguments ) );
-
-				// Make sure to return any of the extra brackets if they
-				// weren't used to escape the shortcode.
-				return result ? left + result + right : match;
-			});
-		},
-
-		// ### Generate a string from shortcode parameters
-		//
-		// Creates a `wp.shortcode` instance and returns a string.
-		//
-		// Accepts the same `options` as the `wp.shortcode()` constructor,
-		// containing a `tag` string, a string or object of `attrs`, a boolean
-		// indicating whether to format the shortcode using a `single` tag, and a
-		// `content` string.
-		string: function( options ) {
-			return new wp.shortcode( options ).string();
-		},
-
-		// ### Generate a RegExp to identify a shortcode
-		//
-		// The base regex is functionally equivalent to the one found in
-		// `get_shortcode_regex()` in `wp-includes/shortcodes.php`.
-		//
-		// Capture groups:
-		//
-		// 1. An extra `[` to allow for escaping shortcodes with double `[[]]`
-		// 2. The shortcode name
-		// 3. The shortcode argument list
-		// 4. The self closing `/`
-		// 5. The content of a shortcode when it wraps some content.
-		// 6. The closing tag.
-		// 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
-		regexp: _.memoize( function( tag ) {
-			return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
-		}),
-
-
-		// ### Parse shortcode attributes
-		//
-		// Shortcodes accept many types of attributes. These can chiefly be
-		// divided into named and numeric attributes:
-		//
-		// Named attributes are assigned on a key/value basis, while numeric
-		// attributes are treated as an array.
-		//
-		// Named attributes can be formatted as either `name="value"`,
-		// `name='value'`, or `name=value`. Numeric attributes can be formatted
-		// as `"value"` or just `value`.
-		attrs: _.memoize( function( text ) {
-			var named   = {},
-				numeric = [],
-				pattern, match;
-
-			// This regular expression is reused from `shortcode_parse_atts()`
-			// in `wp-includes/shortcodes.php`.
-			//
-			// Capture groups:
-			//
-			// 1. An attribute name, that corresponds to...
-			// 2. a value in double quotes.
-			// 3. An attribute name, that corresponds to...
-			// 4. a value in single quotes.
-			// 5. An attribute name, that corresponds to...
-			// 6. an unquoted value.
-			// 7. A numeric attribute in double quotes.
-			// 8. A numeric attribute in single quotes.
-			// 9. An unquoted numeric attribute.
-			pattern = /([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*'([^']*)'(?:\s|$)|([\w-]+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|'([^']*)'(?:\s|$)|(\S+)(?:\s|$)/g;
-
-			// Map zero-width spaces to actual spaces.
-			text = text.replace( /[\u00a0\u200b]/g, ' ' );
-
-			// Match and normalize attributes.
-			while ( (match = pattern.exec( text )) ) {
-				if ( match[1] ) {
-					named[ match[1].toLowerCase() ] = match[2];
-				} else if ( match[3] ) {
-					named[ match[3].toLowerCase() ] = match[4];
-				} else if ( match[5] ) {
-					named[ match[5].toLowerCase() ] = match[6];
-				} else if ( match[7] ) {
-					numeric.push( match[7] );
-				} else if ( match[8] ) {
-					numeric.push( match[8] );
-				} else if ( match[9] ) {
-					numeric.push( match[9] );
-				}
-			}
-
-			return {
-				named:   named,
-				numeric: numeric
-			};
-		}),
-
-		// ### Generate a Shortcode Object from a RegExp match
-		// Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
-		// generated by `wp.shortcode.regexp()`. `match` can also be set to the
-		// `arguments` from a callback passed to `regexp.replace()`.
-		fromMatch: function( match ) {
-			var type;
-
-			if ( match[4] ) {
-				type = 'self-closing';
-			} else if ( match[6] ) {
-				type = 'closed';
-			} else {
-				type = 'single';
-			}
-
-			return new wp.shortcode({
-				tag:     match[2],
-				attrs:   match[3],
-				type:    type,
-				content: match[5]
-			});
-		}
-	};
-
-
-	// Shortcode Objects
-	// -----------------
-	//
-	// Shortcode objects are generated automatically when using the main
-	// `wp.shortcode` methods: `next()`, `replace()`, and `string()`.
-	//
-	// To access a raw representation of a shortcode, pass an `options` object,
-	// containing a `tag` string, a string or object of `attrs`, a string
-	// indicating the `type` of the shortcode ('single', 'self-closing', or
-	// 'closed'), and a `content` string.
-	wp.shortcode = _.extend( function( options ) {
-		_.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) );
-
-		var attrs = this.attrs;
-
-		// Ensure we have a correctly formatted `attrs` object.
-		this.attrs = {
-			named:   {},
-			numeric: []
-		};
-
-		if ( ! attrs ) {
-			return;
-		}
-
-		// Parse a string of attributes.
-		if ( _.isString( attrs ) ) {
-			this.attrs = wp.shortcode.attrs( attrs );
-
-		// Identify a correctly formatted `attrs` object.
-		} else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) {
-			this.attrs = attrs;
-
-		// Handle a flat object of attributes.
-		} else {
-			_.each( options.attrs, function( value, key ) {
-				this.set( key, value );
-			}, this );
-		}
-	}, wp.shortcode );
-
-	_.extend( wp.shortcode.prototype, {
-		// ### Get a shortcode attribute
-		//
-		// Automatically detects whether `attr` is named or numeric and routes
-		// it accordingly.
-		get: function( attr ) {
-			return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ];
-		},
-
-		// ### Set a shortcode attribute
-		//
-		// Automatically detects whether `attr` is named or numeric and routes
-		// it accordingly.
-		set: function( attr, value ) {
-			this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value;
-			return this;
-		},
-
-		// ### Transform the shortcode match into a string
-		string: function() {
-			var text    = '[' + this.tag;
-
-			_.each( this.attrs.numeric, function( value ) {
-				if ( /\s/.test( value ) ) {
-					text += ' "' + value + '"';
-				} else {
-					text += ' ' + value;
-				}
-			});
-
-			_.each( this.attrs.named, function( value, name ) {
-				text += ' ' + name + '="' + value + '"';
-			});
-
-			// If the tag is marked as `single` or `self-closing`, close the
-			// tag and ignore any additional content.
-			if ( 'single' === this.type ) {
-				return text + ']';
-			} else if ( 'self-closing' === this.type ) {
-				return text + ' /]';
-			}
-
-			// Complete the opening tag.
-			text += ']';
-
-			if ( this.content ) {
-				text += this.content;
-			}
-
-			// Add the closing tag.
-			return text + '[/' + this.tag + ']';
-		}
-	});
-}());
-
-// HTML utility functions
-// ----------------------
-//
-// Experimental. These functions may change or be removed in the future.
-(function(){
-	wp.html = _.extend( wp.html || {}, {
-		// ### Parse HTML attributes.
-		//
-		// Converts `content` to a set of parsed HTML attributes.
-		// Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
-		// the HTML attribute specification. Reformats the attributes into an
-		// object that contains the `attrs` with `key:value` mapping, and a record
-		// of the attributes that were entered using `empty` attribute syntax (i.e.
-		// with no value).
-		attrs: function( content ) {
-			var result, attrs;
-
-			// If `content` ends in a slash, strip it.
-			if ( '/' === content[ content.length - 1 ] ) {
-				content = content.slice( 0, -1 );
-			}
-
-			result = wp.shortcode.attrs( content );
-			attrs  = result.named;
-
-			_.each( result.numeric, function( key ) {
-				if ( /\s/.test( key ) ) {
-					return;
-				}
-
-				attrs[ key ] = '';
-			});
-
-			return attrs;
-		},
-
-		// ### Convert an HTML-representation of an object to a string.
-		string: function( options ) {
-			var text = '<' + options.tag,
-				content = options.content || '';
-
-			_.each( options.attrs, function( value, attr ) {
-				text += ' ' + attr;
-
-				// Convert boolean values to strings.
-				if ( _.isBoolean( value ) ) {
-					value = value ? 'true' : 'false';
-				}
-
-				text += '="' + value + '"';
-			});
-
-			// Return the result if it is a self-closing tag.
-			if ( options.single ) {
-				return text + ' />';
-			}
-
-			// Complete the opening tag.
-			text += '>';
-
-			// If `content` is an object, recursively call this function.
-			text += _.isObject( content ) ? wp.html.string( content ) : content;
-
-			return text + '</' + options.tag + '>';
-		}
-	});
-}());
+window.wp.shortcode = require( '@wordpress/shortcode' );
diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php
index 226627ecb0..273eafd91d 100644
--- a/src/wp-includes/script-loader.php
+++ b/src/wp-includes/script-loader.php
@@ -372,6 +372,10 @@ function wp_default_scripts( &$scripts ) {
 	$scripts->add( 'json2', "/wp-includes/js/json2$suffix.js", array(), '2015-05-03' );
 	did_action( 'init' ) && $scripts->add_data( 'json2', 'conditional', 'lt IE 8' );
 
+	$scripts->add( 'wp-polyfill', "/wp-includes/js/wp-polyfill$dev_suffix.js", array(), '7.0.0', 1 );
+	$scripts->add( 'lodash', "/wp-includes/js/lodash$dev_suffix.js", array(), '4.17.11', 1 );
+	did_action( 'init' ) && $scripts->add_inline_script( 'lodash', 'window.lodash = _.noConflict();' );
+
 	$scripts->add( 'underscore', "/wp-includes/js/underscore$dev_suffix.js", array(), '1.8.3', 1 );
 	$scripts->add( 'backbone', "/wp-includes/js/backbone$dev_suffix.js", array( 'underscore', 'jquery' ), '1.2.3', 1 );
 
@@ -696,7 +700,7 @@ function wp_default_scripts( &$scripts ) {
 
 	$scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
 
-	$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
+	$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'lodash', 'wp-polyfill' ), false, 1 );
 	$scripts->add( 'media-models', "/wp-includes/js/media-models$suffix.js", array( 'wp-backbone' ), false, 1 );
 	did_action( 'init' ) && $scripts->localize(
 		'media-models',
diff --git a/tests/qunit/wp-includes/js/shortcode.js b/tests/qunit/wp-includes/js/shortcode.js
deleted file mode 100644
index 2fd3c54d4c..0000000000
--- a/tests/qunit/wp-includes/js/shortcode.js
+++ /dev/null
@@ -1,215 +0,0 @@
-/* global wp, jQuery */
-jQuery( function() {
-	module( 'shortcode' );
-
-	test( 'next() should find the shortcode', function() {
-		var result;
-
-		// Basic
-		result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode' );
-		equal( result.index, 13, 'foo shortcode found at index 13' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [foo param="foo"] shortcode' );
-		equal( result.index, 13, 'foo shortcode with params found at index 13' );
-	});
-
-	test( 'next() should not find shortcodes that are not there', function() {
-		var result;
-
-		// Not found
-		result = wp.shortcode.next( 'bar', 'this has the [foo] shortcode' );
-		equal( result, undefined, 'bar shortcode not found' );
-
-		result = wp.shortcode.next( 'bar', 'this has the [foo param="bar"] shortcode' );
-		equal( result, undefined, 'bar shortcode not found with params' );
-	});
-
-	test( 'next() should find the shortcode when told to start looking beyond the start of the string', function() {
-		var result;
-
-		// Starting at indices
-		result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 12 );
-		equal( result.index, 13, 'foo shortcode found before index 13' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 13 );
-		equal( result.index, 13, 'foo shortcode found at index 13' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 14 );
-		equal( result, undefined, 'foo shortcode not found after index 13' );
-	});
-
-	test( 'next() should find the second instances of the shortcode when the starting indice is after the start of the first one', function() {
-		var result;
-
-		result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode [foo] twice', 14 );
-		equal( result.index, 29, 'foo shortcode found the second foo at index 29' );
-	});
-
-
-	test( 'next() should not find escaped shortcodes', function() {
-		var result;
-
-		// Escaped
-		result = wp.shortcode.next( 'foo', 'this has the [[foo]] shortcode' );
-		equal( result, undefined, 'foo shortcode not found when escaped' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [[foo param="foo"]] shortcode' );
-		equal( result, undefined, 'foo shortcode not found when escaped with params' );
-	});
-
-	test( 'next() should find shortcodes that are incorrectly escaped by newlines', function() {
-		var result;
-
-		result = wp.shortcode.next( 'foo', 'this has the [\n[foo]] shortcode' );
-		equal( result.index, 15, 'shortcode found when incorrectly escaping the start of it' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [[foo]\n] shortcode' );
-		equal( result.index, 14, 'shortcode found when incorrectly escaping the end of it' );
-	});
-
-	test( 'next() should still work when there are not equal ammounts of square brackets', function() {
-		var result;
-
-		result = wp.shortcode.next( 'foo', 'this has the [[foo] shortcode' );
-		equal( result.index, 14, 'shortcode found when there are offset square brackets' );
-
-		result = wp.shortcode.next( 'foo', 'this has the [foo]] shortcode' );
-		equal( result.index, 13, 'shortcode found when there are offset square brackets' );
-	});
-
-	test( 'next() should find the second instances of the shortcode when the first one is escaped', function() {
-		var result;
-
-
-		result = wp.shortcode.next( 'foo', 'this has the [[foo]] shortcode [foo] twice' );
-		equal( result.index, 31, 'foo shortcode found the non-escaped foo at index 31' );
-	});
-
-	test( 'next() should not find shortcodes that are not full matches', function() {
-		var result;
-
-		// Stubs
-		result = wp.shortcode.next( 'foo', 'this has the [foobar] shortcode' );
-		equal( result, undefined, 'stub does not trigger match' );
-
-		result = wp.shortcode.next( 'foobar', 'this has the [foo] shortcode' );
-		equal( result, undefined, 'stub does not trigger match' );
-	});
-
-	test( 'replace() should replace the shortcode', function() {
-		var result;
-
-		// Basic
-		result = wp.shortcode.replace( 'foo', 'this has the [foo] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the bar shortcode', 'foo replaced with bar' );
-
-		result = wp.shortcode.replace( 'foo', 'this has the [foo param="foo"] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the bar shortcode', 'foo and params replaced with bar' );
-	});
-
-	test( 'replace() should not replace the shortcode when it does not match', function() {
-		var result;
-
-		// Not found
-		result = wp.shortcode.replace( 'bar', 'this has the [foo] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [foo] shortcode', 'bar not found' );
-
-		result = wp.shortcode.replace( 'bar', 'this has the [foo param="bar"] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [foo param="bar"] shortcode', 'bar not found with params' );
-	});
-
-	test( 'replace() should replace the shortcode in all instances of its use', function() {
-		var result;
-
-		// Multiple instances
-		result = wp.shortcode.replace( 'foo', 'this has the [foo] shortcode [foo] twice', shortcodeReplaceCallback );
-		equal( result, 'this has the bar shortcode bar twice', 'foo replaced with bar twice' );
-
-		result = wp.shortcode.replace( 'foo', 'this has the [foo param="foo"] shortcode [foo] twice', shortcodeReplaceCallback );
-		equal( result, 'this has the bar shortcode bar twice', 'foo and params replaced with bar twice' );
-	});
-
-	test( 'replace() should not replace the escaped shortcodes', function() {
-		var result;
-
-		// Escaped
-		result = wp.shortcode.replace( 'foo', 'this has the [[foo]] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [[foo]] shortcode', 'escaped foo not replaced' );
-
-		result = wp.shortcode.replace( 'foo', 'this has the [[foo param="bar"]] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [[foo param="bar"]] shortcode', 'escaped foo with params not replaced' );
-
-		result = wp.shortcode.replace( 'foo', 'this [foo] has the [[foo param="bar"]] shortcode escaped', shortcodeReplaceCallback );
-		equal( result, 'this bar has the [[foo param="bar"]] shortcode escaped', 'escaped foo with params not replaced but unescaped foo replaced' );
-	});
-
-	test( 'replace() should replace improperly escaped shortcodes that include newlines', function() {
-		var result;
-
-		result = wp.shortcode.replace( 'foo', 'this [foo] has the [[foo param="bar"]\n] shortcode ', shortcodeReplaceCallback );
-		equal( result, 'this bar has the [bar\n] shortcode ', 'escaping with newlines should not actually escape the content' );
-
-		result = wp.shortcode.replace( 'foo', 'this [foo] has the [\n[foo param="bar"]] shortcode ', shortcodeReplaceCallback );
-		equal( result, 'this bar has the [\nbar] shortcode ', 'escaping with newlines should not actually escape the content' );
-	});
-
-	test( 'replace() should not replace the shortcode when it is an incomplete match', function() {
-		var result;
-
-		// Stubs
-		result = wp.shortcode.replace( 'foo', 'this has the [foobar] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [foobar] shortcode', 'stub not replaced' );
-
-		result = wp.shortcode.replace( 'foobar', 'this has the [foo] shortcode', shortcodeReplaceCallback );
-		equal( result, 'this has the [foo] shortcode', 'stub not replaced' );
-	});
-
-    // A callback function for the replace tests
-	function shortcodeReplaceCallback( ) {
-		return 'bar';
-	}
-
-    test( 'attrs() should return named attributes created with single, double, and no quotes', function() {
-        var expected = {
-            'named': {
-                'param': 'foo',
-                'another': 'bar',
-                'andagain': 'baz'
-            }, 'numeric' : []
-        };
-
-        deepEqual( wp.shortcode.attrs('param="foo" another=\'bar\' andagain=baz'), expected, 'attr parsed all three named types');
-    });
-
-    test( 'attrs() should return numeric attributes in the order they are used', function() {
-        var expected = {
-            'named': {}, 'numeric' : ['foo', 'bar', 'baz']
-        };
-
-        deepEqual( wp.shortcode.attrs('foo bar baz'), expected, 'attr parsed numeric attributes');
-    });
-
-    test( 'attrs() should return numeric attributes in the order they are used when they have named attributes in between', function() {
-        var expected = {
-            'named': { 'not': 'a blocker'  }, 'numeric' : ['foo', 'bar', 'baz']
-        };
-
-        deepEqual( wp.shortcode.attrs('foo not="a blocker" bar baz'), expected, 'attr parsed numeric attributes');
-    });
-
-	test( 'attrs() should return numeric attributes created with single, double, and no quotes', function() {
-		var expected = {
-			'named': {}, 'numeric' : ['foo', 'bar', 'baz']
-		};
-
-		deepEqual( wp.shortcode.attrs('foo "bar" \'baz\''), expected, 'attr parsed numeric attributes');
-	});
-	
-	test( 'attrs() should return mixed attributes created with single, double, and no quotes', function() {
-		var expected = {
-			'named': { a: 'foo', b: 'bar', c: 'baz' }, 'numeric' : ['foo', 'bar', 'baz']
-		};
-
-		deepEqual( wp.shortcode.attrs('a="foo" b=\'bar\' c=baz foo "bar" \'baz\''), expected, 'attr parsed numeric attributes');
-	});
-});
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index cf6fa9ea88..e2d16aa134 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -1,5 +1,5 @@
 var path        	= require( 'path' ),
-	webpack       	= require( 'webpack' ),
+	webpack         = require('webpack'),
 	admin_files   	= {},
 	include_files 	= {};
 
@@ -12,16 +12,55 @@ include_files = {
 	'build/wp-includes/js/media-models.min.js': ['./src/js/_enqueues/wp/media/models.js'],
 	'build/wp-includes/js/media-views.js': ['./src/js/_enqueues/wp/media/views.js'],
 	'build/wp-includes/js/media-views.min.js': ['./src/js/_enqueues/wp/media/views.js'],
+	'build/wp-includes/js/shortcode.js': ['./src/js/_enqueues/wp/shortcode.js'],
+	'build/wp-includes/js/shortcode.min.js': ['./src/js/_enqueues/wp/shortcode.js'],
 };
 
+const vendors = {
+	'lodash': 'lodash',
+	'wp-polyfill': '@babel/polyfill'
+};
+
+const externals = {
+	lodash: 'lodash',
+	'lodash-es': 'lodash',
+};
+
+/**
+ * Given a string, returns a new string with dash separators converedd to
+ * camel-case equivalent. This is not as aggressive as `_.camelCase` in
+ * converting to uppercase, where Lodash will convert letters following
+ * numbers.
+ *
+ * @param {string} string Input dash-delimited string.
+ *
+ * @return {string} Camel-cased string.
+ */
+function camelCaseDash( string ) {
+	return string.replace(
+		/-([a-z])/g,
+		( match, letter ) => letter.toUpperCase()
+	);
+}
+
 module.exports = [
 	{
 		cache: true,
 		watch: false,
-		entry: Object.assign( admin_files, include_files ),
+		entry: Object.assign(
+			admin_files,
+			include_files,
+			Object.keys(vendors).reduce( ( memo, fileName ) => {
+				const packageName = vendors[ fileName ];
+				memo[ `build/wp-includes/js/${ fileName }.js` ] = `./node_modules/${ packageName }`;
+				memo[ `build/wp-includes/js/${ fileName }.min.js` ] = `./node_modules/${ packageName }`;
+				return memo;
+			}, {} )
+		),
 		output: {
 			filename: '[name]',
 		},
+		externals,
 		plugins: [
 			new webpack.optimize.UglifyJsPlugin({
 				include: /\.min\.js$/,
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index e6ba61f27e..1e2f71f190 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -1,33 +1,9 @@
-var path        	= require( 'path' ),
-	webpack       	= require( 'webpack' ),
-	admin_files   	= {},
-	include_files 	= {};
+const webpack = require( 'webpack' )
+const config  = require( './webpack.config.dev' );
 
-include_files = {
-	'build/wp-includes/js/media-audiovideo.js': ['./src/js/_enqueues/wp/media/audiovideo.js'],
-	'build/wp-includes/js/media-audiovideo.min.js': ['./src/js/_enqueues/wp/media/audiovideo.js'],
-	'build/wp-includes/js/media-grid.js': ['./src/js/_enqueues/wp/media/grid.js'],
-	'build/wp-includes/js/media-grid.min.js': ['./src/js/_enqueues/wp/media/grid.js'],
-	'build/wp-includes/js/media-models.js': ['./src/js/_enqueues/wp/media/models.js'],
-	'build/wp-includes/js/media-models.min.js': ['./src/js/_enqueues/wp/media/models.js'],
-	'build/wp-includes/js/media-views.js': ['./src/js/_enqueues/wp/media/views.js'],
-	'build/wp-includes/js/media-views.min.js': ['./src/js/_enqueues/wp/media/views.js'],
-};
-
-module.exports = [
-	{
-		cache: true,
-		watch: false,
-		entry: Object.assign( admin_files, include_files ),
-		output: {
-			filename: '[name]',
-		},
-		plugins: [
-			new webpack.optimize.ModuleConcatenationPlugin(),
-			new webpack.optimize.UglifyJsPlugin({
-				include: /\.min\.js$/,
-				minimize: true
-			})
-		]
-	}
+config[0].plugins = [
+	new webpack.optimize.ModuleConcatenationPlugin(),
+	...config[0].plugins
 ];
+
+module.exports = config;
\ No newline at end of file
