diff --git src/wp-admin/includes/file.php src/wp-admin/includes/file.php
index 05bfde46a4..6c15bded98 100644
--- src/wp-admin/includes/file.php
+++ src/wp-admin/includes/file.php
@@ -70,7 +70,7 @@ $wp_file_descriptions = array(
  * @since 1.5.0
  *
  * @global array $wp_file_descriptions Theme file descriptions.
- * @global array $allowed_files        List of allowed files. 
+ * @global array $allowed_files        List of allowed files.
  * @param string $file Filesystem path or filename
  * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
  *                Appends 'Page Template' to basename of $file if the file is a page template
@@ -126,23 +126,41 @@ function get_home_path() {
  * @return bool|array False on failure, Else array of files
  */
 function list_files( $folder = '', $levels = 100 ) {
-	if ( empty($folder) )
+	if ( empty( $folder ) ) {
 		return false;
+	}
 
-	if ( ! $levels )
+	if ( ! $levels ) {
 		return false;
+	}
+
+	/**
+	 * Filters the array of excluded directories and files while scanning the folder.
+	 *
+	 * @since 4.9
+	 *
+	 * @param array $exclusions Array of excluded directories and files.
+	 */
+	$exclusions = (array) apply_filters( 'list_files_exclusions', array( 'CVS', 'node_modules' ) );
 
 	$files = array();
 	if ( $dir = @opendir( $folder ) ) {
-		while (($file = readdir( $dir ) ) !== false ) {
-			if ( in_array($file, array('.', '..') ) )
+		while ( ( $file = readdir( $dir ) ) !== false ) {
+			if ( in_array( $file, array( '.', '..' ), true ) ) {
+				continue;
+			}
+
+			if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
 				continue;
+			}
+
 			if ( is_dir( $folder . '/' . $file ) ) {
-				$files2 = list_files( $folder . '/' . $file, $levels - 1);
-				if ( $files2 )
-					$files = array_merge($files, $files2 );
-				else
+				$files2 = list_files( $folder . '/' . $file, $levels - 1 );
+				if ( $files2 ) {
+					$files = array_merge( $files, $files2 );
+				} else {
 					$files[] = $folder . '/' . $file . '/';
+				}
 			} else {
 				$files[] = $folder . '/' . $file;
 			}
diff --git src/wp-admin/includes/plugin.php src/wp-admin/includes/plugin.php
index 97dd767850..802f9884be 100644
--- src/wp-admin/includes/plugin.php
+++ src/wp-admin/includes/plugin.php
@@ -190,35 +190,30 @@ function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup
  * @param string $plugin Path to the main plugin file from plugins directory.
  * @return array List of files relative to the plugin root.
  */
-function get_plugin_files($plugin) {
+function get_plugin_files( $plugin ) {
 	$plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
-	$dir = dirname($plugin_file);
-	$plugin_files = array($plugin);
-	if ( is_dir($dir) && $dir != WP_PLUGIN_DIR ) {
-		$plugins_dir = @ opendir( $dir );
-		if ( $plugins_dir ) {
-			while (($file = readdir( $plugins_dir ) ) !== false ) {
-				if ( substr($file, 0, 1) == '.' )
-					continue;
-				if ( is_dir( $dir . '/' . $file ) ) {
-					$plugins_subdir = @ opendir( $dir . '/' . $file );
-					if ( $plugins_subdir ) {
-						while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
-							if ( substr($subfile, 0, 1) == '.' )
-								continue;
-							$plugin_files[] = plugin_basename("$dir/$file/$subfile");
-						}
-						@closedir( $plugins_subdir );
-					}
-				} else {
-					if ( plugin_basename("$dir/$file") != $plugin )
-						$plugin_files[] = plugin_basename("$dir/$file");
-				}
-			}
-			@closedir( $plugins_dir );
-		}
+	$dir = dirname( $plugin_file );
+
+	$data = get_plugin_data( $plugin_file );
+	$label = isset( $data['Version'] ) ? 'list_files_cache_' . $dir . '-' . $data['Version'] : 'list_files_cache_' . $dir;
+
+	$plugin_files = get_transient( $label );
+	if ( ! empty( $plugin_files ) ) {
+		return $plugin_files;
+	}
+
+	$plugin_files = array( $plugin );
+	if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) {
+
+		$list_files = list_files( $dir );
+		$list_files = array_map( 'plugin_basename', $list_files );
+
+		$plugin_files += $list_files;
+		$plugin_files = array_unique( $plugin_files );
 	}
 
+	set_transient( $label, $plugin_files, HOUR_IN_SECONDS );
+
 	return $plugin_files;
 }
 
diff --git src/wp-admin/theme-editor.php src/wp-admin/theme-editor.php
index 2a593dee64..3247bb7d14 100644
--- src/wp-admin/theme-editor.php
+++ src/wp-admin/theme-editor.php
@@ -120,16 +120,16 @@ $file_types = array_unique( array_merge( $file_types, $default_types ) );
 foreach ( $file_types as $type ) {
 	switch ( $type ) {
 		case 'php':
-			$allowed_files += $theme->get_files( 'php', 1 );
+			$allowed_files += $theme->get_files( 'php', -1 );
 			$has_templates = ! empty( $allowed_files );
 			break;
 		case 'css':
-			$style_files = $theme->get_files( 'css' );
+			$style_files = $theme->get_files( 'css', -1 );
 			$allowed_files['style.css'] = $style_files['style.css'];
 			$allowed_files += $style_files;
 			break;
 		default:
-			$allowed_files += $theme->get_files( $type );
+			$allowed_files += $theme->get_files( $type, -1 );
 			break;
 	}
 }
diff --git src/wp-includes/class-wp-theme.php src/wp-includes/class-wp-theme.php
index 3e6c62906b..1ae904579e 100644
--- src/wp-includes/class-wp-theme.php
+++ src/wp-includes/class-wp-theme.php
@@ -984,10 +984,33 @@ final class WP_Theme implements ArrayAccess {
 	 * 	             being absolute paths.
 	 */
 	public function get_files( $type = null, $depth = 0, $search_parent = false ) {
-		$files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
+		// get and cache all theme files to start with.
+		$label = 'list_files_cache_' . $this->get( 'Name' ) . '-' . $this->get( 'Version' );
+		$all_files = get_transient( $label );
+		if ( empty( $all_files ) ) {
+			$all_files = (array) self::scandir( $this->get_stylesheet_directory(), null, -1 );
+
+			if ( $search_parent && $this->parent() ) {
+				$all_files += (array) self::scandir( $this->get_template_directory(), null, -1 );
+			}
+
+			set_transient( $label, $all_files, HOUR_IN_SECONDS );
+		}
 
-		if ( $search_parent && $this->parent() )
-			$files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
+		// Filter $all_files by $type & $depth.
+		$files = array();
+		if ( $type ) {
+			$type = (array) $type;
+			$_extensions = implode( '|', $type );
+		}
+		foreach ( $all_files as $key => $file ) {
+			if ( $depth >= 0 && substr_count( $key, '/' ) > $depth ) {
+				continue; // Filter by depth.
+			}
+			if ( ! $type || preg_match( '~\.(' . $_extensions . ')$~', $file ) ) { // Filter by type.
+				$files[ $key ] = $file;
+			}
+		}
 
 		return $files;
 	}
