The fastest way to load pages in WordPress https://habd.as/code/hyperdrive/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hyperdrive.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /**
  3. * Putting WordPress into Hyperdrive.
  4. *
  5. * @package Hyperdrive
  6. * @author Josh Habdas
  7. * @since 1.0.0
  8. * @license GPL-3.0
  9. *
  10. * Plugin Name: Hyperdrive
  11. * Plugin URI: https://github.com/comfusion/hyperdrive
  12. * Description: The fastest way to load pages in WordPress.
  13. * Author: Josh Habdas
  14. * Author URI: https://habd.as
  15. * Text Domain: hyperdrive
  16. * Version: 1.0.0-beta.3
  17. * License: GPL-3.0
  18. *
  19. * Hyperdrive. The fastest way to load pages in WordPress.
  20. * Copyright (C) 2017 Josh Habdas and contributors
  21. *
  22. * This program is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU General Public License as published by
  24. * the Free Software Foundation, either version 3 of the License, or
  25. * (at your option) any later version.
  26. *
  27. * This program is distributed in the hope that it will be useful, but
  28. * WITHOUT ANY WARRANTY; without even the implied warranty of
  29. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  30. * General Public License for more details.
  31. *
  32. * You should have received a copy of the GNU General Public License
  33. * along with this program. If not, see
  34. * <https://opensource.org/licenses/GPL-3.0>.
  35. */
  36. namespace hyperdrive;
  37. defined( 'ABSPATH' ) or die( 'Now you are going to die! BAM!' );
  38. /**
  39. * Defines Hyperdrive version output to source.
  40. *
  41. * @since 1.0.0
  42. * @var HYPERDRIVE_VERSION Semantic program version.
  43. */
  44. const HYPERDRIVE_VERSION = '1.0.0-beta.3';
  45. /**
  46. * Engages Hyperdrive.
  47. *
  48. * Engages Hyperdrive while printing scripts or data
  49. * in the head tag on the front end.
  50. *
  51. * @since 1.0.0
  52. */
  53. add_action( 'wp_head', __NAMESPACE__ . '\engage' );
  54. /**
  55. * Calibrates Hyperdrive thrusters.
  56. *
  57. * Creates an associative array containing structured data required
  58. * for Fetch Injection. Also dequeues enqueued scripts so WordPress
  59. * doesn't load them. Data structure is assumed by functions using
  60. * and used by this method.
  61. *
  62. * @since Hyperdrive 1.0.0
  63. * @return Associative array containing thruster calibration data.
  64. *
  65. * Example structured data ("Calibration data"):
  66. *
  67. * array(
  68. * string "jquery-scrollto",
  69. * string "/assets/js/jquery.scrollTo.js?ver=2.1.2",
  70. * array(
  71. * string "jquery",
  72. * string ""
  73. * array(
  74. * array(
  75. * string "jquery-core",
  76. * string "/wp-includes/js/jquery/jquery.js?ver=1.12.4",
  77. * array(0)
  78. * )
  79. * array(
  80. * string "jquery-migrate",
  81. * string "/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1",
  82. * array(0)
  83. * )
  84. * )
  85. * )
  86. * );
  87. */
  88. function calibrate_thrusters() {
  89. $calibration_data = [];
  90. $scripts = get_enqueued_scripts();
  91. foreach ( $scripts as $script ) {
  92. if ( empty( $script->extra['conditional'] ) ) {
  93. // It's a good thing you were wearing that helmet.
  94. $calibration_data[] = array(
  95. $script->handle,
  96. get_src_for_handle( $script->handle ),
  97. get_dependency_data( $script->deps ),
  98. );
  99. // Not in here, mister! This is a Mercedes!
  100. wp_dequeue_script( $script->handle );
  101. }
  102. }
  103. return $calibration_data;
  104. }
  105. /**
  106. * Generates antimatter particles.
  107. *
  108. * Translates thruster calibration data into an antimatter
  109. * particle array and dedupes it while respecting sort order.
  110. *
  111. * @since Hyperdrive 1.0.0
  112. *
  113. * @param array $calibration_data Thurster calibration settings.
  114. * @param boolean $recursing True when generating subparticles.
  115. * @return A list of scripts for use in Fetch Injection.
  116. */
  117. function generate_antimatter( $calibration_data, $recursing = false ) {
  118. $particle_array = [];
  119. foreach ( $calibration_data as $idx => $data ) {
  120. $handle = $data[0];
  121. $url = $data[1];
  122. $particle_array[] = "{$url}";
  123. $subparticles = $data[2];
  124. if ( $subparticles ) {
  125. $particle_array[] = generate_antimatter( $subparticles, true );
  126. }
  127. }
  128. // Remove numeric array keys.
  129. array_multisort( $particle_array );
  130. // Remove duplicate values.
  131. $particle_array = array_map(
  132. 'unserialize', array_unique(
  133. array_map( 'serialize', $particle_array )
  134. )
  135. );
  136. return $particle_array;
  137. }
  138. /**
  139. * Converts antimatter particles into dark matter.
  140. *
  141. * Takes an antimatter particle array transforms it into something
  142. * Fetch Inject understands, making FTL a future possibility.
  143. *
  144. * @since Hyperdrive 1.0.0
  145. * @link https://github.com/jhabdas/fetch-inject
  146. *
  147. * @todo Consolidate dedupe logic with `generate_antimatter`.
  148. *
  149. * @param array $antimatter_particles Partical array.
  150. * @return A string containing a fully-assembled inline script.
  151. */
  152. function fold_spacetime( $antimatter_particles ) {
  153. $injectors = $particle_array = []; // @codingStandardsIgnoreLine
  154. $fetch_inject_string = '';
  155. /**
  156. * Create ordered array of JSON encoded strings for Fetch Injection.
  157. *
  158. * @param array $array Multidimensional array of antimatter particles.
  159. * @param array $accumulator Accumulates particles during recursion.
  160. * @param array $injectors JSON-encoded strings for Fetch Injection.
  161. * @param array $particle_array I'm not sure why this is here. See @todo above.
  162. * @param array $injection_json JSON-encoded representation of $accumulator.
  163. */
  164. function walk_recursive( $array, $accumulator, &$injectors, &$particle_array, &$injection_json = '' ) {
  165. $accumulator = [];
  166. array_walk( $array, function( $item ) use ( &$accumulator, &$injectors, &$particle_array, &$injection_json ) {
  167. if ( ! empty( $item ) ) {
  168. if ( is_array( $item ) ) {
  169. walk_recursive( $item, $accumulator, $injectors, $particle_array, $injection_json );
  170. } else {
  171. if ( ! in_multi_array( $item, $particle_array ) ) {
  172. $accumulator[] = $particle_array[] = $item; // @codingStandardsIgnoreLine
  173. }
  174. }
  175. }
  176. });
  177. if ( ! empty( $accumulator ) ) {
  178. $injection_json = json_encode( $accumulator, JSON_UNESCAPED_SLASHES );
  179. $injectors[] = $injection_json;
  180. }
  181. }
  182. walk_recursive( $antimatter_particles, false, $injectors, $particle_array );
  183. /**
  184. * Assemble Fetch Inject string using ordered array.
  185. */
  186. $first_element = reset( $injectors );
  187. $last_element = end( $injectors );
  188. foreach ( $injectors as $idx => $injector ) {
  189. if ( $injector === $first_element ) {
  190. $fetch_inject_string = "fetchInject($injector)";
  191. } elseif ( $injector === $last_element ) {
  192. $fetch_inject_string = "fetchInject($injector, $fetch_inject_string)";
  193. } else {
  194. $array_with_empty_string = array( '' ); // Like WordPress core jquery handle.
  195. if ( ! (json_decode( $injector ) === $array_with_empty_string) ) {
  196. $fetch_inject_string = "fetchInject($injector, $fetch_inject_string)";
  197. }
  198. }
  199. }
  200. $hyperdrive_ver = HYPERDRIVE_VERSION;
  201. return <<<EOD
  202. /*!
  203. * Hyperdrive v$hyperdrive_ver
  204. * Copyright (c) 2017 Josh Habdas
  205. * @license GPL-3.0
  206. */
  207. (function () {
  208. if (!window.fetch) return;
  209. /**
  210. * Fetch Inject v1.6.11
  211. * Copyright (c) 2017 Josh Habdas
  212. * @licence ISC
  213. */
  214. var fetchInject=function(){"use strict";const e=function(e,t,n,r,o,i,c){i=t.createElement(n),c=t.getElementsByTagName(n)[0],i.appendChild(t.createTextNode(r.text)),i.onload=o(r),c?c.parentNode.insertBefore(i,c):t.head.appendChild(i)},t=function(t,n){if(!t||!Array.isArray(t))return Promise.reject(new Error("`inputs` must be an array"));if(n&&!(n instanceof Promise))return Promise.reject(new Error("`promise` must be a promise"));const r=[],o=n?[].concat(n):[],i=[];return t.forEach(e=>o.push(window.fetch(e).then(e=>{return[e.clone().text(),e.blob()]}).then(e=>{return Promise.all(e).then(e=>{r.push({text:e[0],blob:e[1]})})}))),Promise.all(o).then(()=>{return r.forEach(t=>{i.push({then:n=>{"text/css"===t.blob.type?e(window,document,"style",t,n):e(window,document,"script",t,n)}})}),Promise.all(i)})};return t}();
  215. $fetch_inject_string;
  216. })();
  217. EOD;
  218. }
  219. /**
  220. * Enter hyperspace.
  221. *
  222. * Echos an inline script into the document.
  223. *
  224. * @since Hyperdrive 1.0.0
  225. * @param string $dark_energy An inline script to asynchronously
  226. * fetch previously enqueued page resources.
  227. */
  228. function enter_hyperspace( $dark_energy ) {
  229. echo "<script>{$dark_energy}</script>";
  230. }
  231. /**
  232. * Main function engages the hyperdrive.
  233. *
  234. * @since Hyperdrive 1.0.0
  235. *
  236. * @todo return void (requires PHP 7.1).
  237. */
  238. function engage() {
  239. $calibration_data = calibrate_thrusters();
  240. $antimatter_particles = generate_antimatter( $calibration_data );
  241. $dark_energy = fold_spacetime( $antimatter_particles );
  242. enter_hyperspace( $dark_energy );
  243. }
  244. /**
  245. * Gets dependency data recursively.
  246. *
  247. * @since Hyperdrive 1.0.0
  248. *
  249. * @param array(string) $handles An array of handles.
  250. * @return array(array) Dependency data matching expected structure.
  251. */
  252. function get_dependency_data( $handles ) {
  253. $dependency_data = [];
  254. foreach ( $handles as $idx => $handle ) {
  255. $source_url = get_src_for_handle( $handle );
  256. if ( $source_url ) {
  257. $dependency_data[] = array(
  258. $handle,
  259. $source_url,
  260. array(), // Maintain thrust.
  261. );
  262. }
  263. $deps = get_deps_for_handle( $handle );
  264. if ( count( $deps ) > 0 ) {
  265. $dependency_data[] = array(
  266. $handle,
  267. '', // Maintain thrust.
  268. get_dependency_data( $deps ),
  269. );
  270. }
  271. }
  272. return $dependency_data;
  273. }
  274. /**
  275. * Gets scripts registered and enqueued.
  276. *
  277. * @since Hyperdrive 1.0.0
  278. * @return array(_WP_Dependency) A list of enqueued dependencies.
  279. */
  280. function get_enqueued_scripts() {
  281. $wp_scripts = wp_scripts();
  282. foreach ( $wp_scripts->queue as $handle ) {
  283. $enqueued_scripts[] = $wp_scripts->registered[ $handle ];
  284. }
  285. return $enqueued_scripts;
  286. }
  287. /**
  288. * Gets a script dependency for a handle.
  289. *
  290. * @since Hyperdrive 1.0.0
  291. *
  292. * @param string $handle The handle.
  293. * @return _WP_Dependency associated with input handle.
  294. */
  295. function get_dep_for_handle( $handle ) {
  296. $wp_scripts = wp_scripts();
  297. return $wp_scripts->registered[ $handle ];
  298. }
  299. /**
  300. * Gets the source URL given a script handle.
  301. *
  302. * @since Hyperdrive 1.0.0
  303. *
  304. * @param string $handle The handle.
  305. * @return URL associated with handle, or empty string.
  306. */
  307. function get_src_for_handle( $handle ) {
  308. $dep = get_dep_for_handle( $handle );
  309. $suffix = ( $dep->src && $dep->ver )
  310. ? "?ver={$dep->ver}"
  311. : '';
  312. return "{$dep->src}{$suffix}";
  313. }
  314. /**
  315. * Gets all dependencies for a given handle.
  316. *
  317. * @since Hyperdrive 1.0.0
  318. *
  319. * @param string $handle The handle.
  320. * @return array(string) List of handles for dependencies of `$handle`.
  321. */
  322. function get_deps_for_handle( $handle ) {
  323. $dep = get_dep_for_handle( $handle );
  324. return $dep->deps;
  325. }
  326. /**
  327. * Checks if a value exists in a multidimensional array.
  328. *
  329. * @since Hyperdrive 1.0.0
  330. *
  331. * @todo Eliminate multiple return statements.
  332. *
  333. * @param string/array $needle The value(s) to search for.
  334. * @param array $haystack The array to search.
  335. * @return boolean True if found, false otherwise.
  336. */
  337. function in_multi_array( $needle, $haystack ) {
  338. foreach ( $haystack as $item ) {
  339. if ( is_array( $item ) && in_multi_array( $needle, $item ) ) {
  340. return true;
  341. } elseif ( $item == $needle ) {
  342. return true;
  343. }
  344. }
  345. return false;
  346. }