From 73d3dfecc601622413c6939e64e1858cda4abc1a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jul 2019 09:35:22 +0800 Subject: [PATCH] MDL-62497 javascript: lazy load js modules when cachejs is disabled --- lib/jssourcemap.php | 95 ++++++++------------------------------------- lib/requirejs.php | 78 +++++++++++++++---------------------- 2 files changed, 49 insertions(+), 124 deletions(-) diff --git a/lib/jssourcemap.php b/lib/jssourcemap.php index 8473fd19675..3a1c0d59c0a 100644 --- a/lib/jssourcemap.php +++ b/lib/jssourcemap.php @@ -26,10 +26,8 @@ // comment out when debugging or better look into error log! define('NO_DEBUG_DISPLAY', true); -// We need just the values from config.php and minlib.php. -define('ABORT_AFTER_CONFIG', true); -require('../config.php'); // This stops immediately at the beginning of lib/setup.php. -require_once("$CFG->dirroot/lib/jslib.php"); +require('../config.php'); +require_once("$CFG->dirroot/lib/configonlylib.php"); require_once("$CFG->dirroot/lib/classes/requirejs.php"); $slashargument = min_get_slash_argument(); @@ -39,95 +37,36 @@ if (!$slashargument) { } $slashargument = ltrim($slashargument, '/'); -if (substr_count($slashargument, '/') < 1) { - header('HTTP/1.0 404 not found'); - die('Slash argument must contain both a revision and a file path'); -} // Split into revision and module name. -list($rev, $file) = explode('/', $slashargument, 2); -$rev = min_clean_param($rev, 'INT'); +[$file] = explode('/', $slashargument, 1); $file = '/' . min_clean_param($file, 'SAFEPATH'); // Only load js files from the js modules folder from the components. -$jsfiles = array(); -list($unused, $component, $module) = explode('/', $file, 3); +[$unused, $component, $module] = explode('/', $file, 3); // No subdirs allowed - only flat module structure please. if (strpos('/', $module) !== false) { die('Invalid module'); } -// Some (huge) modules are better loaded lazily (when they are used). If we are requesting -// one of these modules, only return the one module, not the combo. -$lazysuffix = "-lazy.js"; -$lazyload = (strpos($module, $lazysuffix) !== false); +// When running a lazy load, we only deal with one file so we can just return the working sourcemap. +$jsfiles = core_requirejs::find_one_amd_module($component, $module, false); +$jsfile = reset($jsfiles); -if ($lazyload) { - $jsfiles = core_requirejs::find_one_amd_module($component, $module, false); -} else { - $jsfiles = core_requirejs::find_all_amd_modules(false); -} +$mapfile = $jsfile . '.map'; -// Create the empty source map. -$map = [ - 'version' => 3, - 'file' => $CFG->wwwroot . '/lib/requirejs.php/' . $slashargument, - 'sections' => [] -]; +if (file_exists($mapfile)) { + $mapdata = file_get_contents($mapfile); + $mapdata = json_decode($mapdata, true); -$line = 0; -// Sort the files to ensure consistent ordering for source map generation. -asort($jsfiles); - -foreach ($jsfiles as $modulename => $jsfile) { $shortfilename = str_replace($CFG->dirroot, '', $jsfile); $srcfilename = str_replace('/amd/build/', '/amd/src/', $shortfilename); $srcfilename = str_replace('.min.js', '.js', $srcfilename); + $fullsrcfilename = $CFG->wwwroot . $srcfilename; + $mapdata['sources'][0] = $fullsrcfilename; - $mapfile = $jsfile . '.map'; - if (file_exists($mapfile)) { - $mapdata = file_get_contents($mapfile); - $mapdata = json_decode($mapdata, true); - unset($mapdata['sourcesContent']); - $mapdata['sources'][0] = $CFG->wwwroot . $srcfilename; - - $map['sections'][] = [ - 'offset' => [ - 'line' => $line, - 'column' => 0 - ], - 'map' => $mapdata - ]; - - $js = file_get_contents($jsfile); - // Remove source map link. - $js = preg_replace('~//# sourceMappingURL.*$~s', '', $js); - } else { - // No sourcemap for this section which means we will have returned the original - // source file to the browser. We have to provide an empty source map to - // ensure that this section is not treated as part of the previous map. - $map['sections'][] = [ - 'offset' => [ - 'line' => $line, - 'column' => 0, - ], - 'map' => [ - 'version' => 3, - 'file' => $shortfilename, - 'sources' => [$CFG->wwwroot . $srcfilename], - 'sourcesContent' => [null], - 'names' => [], - 'mappings' => '' - ] - ]; - // Load the original source file to calculate the number of lines we'll need to - // skip forward for the next source map section. - $js = file_get_contents($CFG->dirroot . $srcfilename); - } - - $js = rtrim($js); - - $line += substr_count($js, "\n") + 1; + echo json_encode($mapdata); +} else { + // If there is no source map file, then we will not generate one for you, sorry. + header('HTTP/1.0 404 not found'); } - -js_send_uncached(json_encode($map), 'jssourcemap.php'); diff --git a/lib/requirejs.php b/lib/requirejs.php index 96b4cfc86d0..047fa3f8824 100644 --- a/lib/requirejs.php +++ b/lib/requirejs.php @@ -57,22 +57,22 @@ if (strpos('/', $module) !== false) { die('Invalid module'); } -// Some (huge) modules are better loaded lazily (when they are used). If we are requesting -// one of these modules, only return the one module, not the combo. -$lazysuffix = "-lazy.js"; -$lazyload = (strpos($module, $lazysuffix) !== false); - -if ($lazyload) { - // We are lazy loading a single file - so include the component/filename pair in the etag. - $etag = sha1($rev . '/' . $component . '/' . $module); -} else { - // We loading all (non-lazy) files - so only the rev makes this request unique. - $etag = sha1($rev); -} - - // Use the caching only for meaningful revision numbers which prevents future cache poisoning. if ($rev > 0 and $rev < (time() + 60 * 60)) { + // This is "production mode". + // Some (huge) modules are better loaded lazily (when they are used). If we are requesting + // one of these modules, only return the one module, not the combo. + $lazysuffix = "-lazy.js"; + $lazyload = (strpos($module, $lazysuffix) !== false); + + if ($lazyload) { + // We are lazy loading a single file - so include the component/filename pair in the etag. + $etag = sha1($rev . '/' . $component . '/' . $module); + } else { + // We loading all (non-lazy) files - so only the rev makes this request unique. + $etag = sha1($rev); + } + $candidate = $CFG->localcachedir . '/requirejs/' . $etag; if (file_exists($candidate)) { @@ -131,27 +131,13 @@ if ($rev > 0 and $rev < (time() + 60 * 60)) { } } -// Ok, now we need to start normal moodle because we need access to the autoloader. -define('ABORT_AFTER_CONFIG_CANCEL', true); -// Session not used here. -define('NO_MOODLE_COOKIES', true); -// Ignore upgrade check. -define('NO_UPGRADE_CHECK', true); +// If we've made it here then we're in "dev mode" where everything is lazy loaded. +// So all files will be served one at a time. +$jsfiles = core_requirejs::find_one_amd_module($component, $module, false); -require("$CFG->dirroot/lib/setup.php"); - -if ($lazyload) { - $jsfiles = core_requirejs::find_one_amd_module($component, $module, false); -} else { - $jsfiles = core_requirejs::find_all_amd_modules(false); -} - -// The content of the resulting file. -$result = []; -// Sort the files to ensure consistent ordering for source map generation. -asort($jsfiles); - -foreach ($jsfiles as $modulename => $jsfile) { +if (!empty($jsfiles)) { + $modulename = array_keys($jsfiles)[0]; + $jsfile = $jsfiles[$modulename]; $shortfilename = str_replace($CFG->dirroot, '', $jsfile); $mapfile = $jsfile . '.map'; @@ -159,9 +145,13 @@ foreach ($jsfiles as $modulename => $jsfile) { // We've got a a source map file so we can return the minified file here and // the source map will be used by the browser to debug. $js = file_get_contents($jsfile); - // Remove source map link from the individual file because we add back a single - // source map link to the whole file at the end. - $js = preg_replace('~//# sourceMappingURL.*$~s', '', $js); + // Fix the source map link for the file. + $js = preg_replace( + '~//# sourceMappingURL.*$~s', + "//# sourceMappingURL={$CFG->wwwroot}/lib/jssourcemap.php{$file}", + $js + ); + $js = rtrim($js); } else { // This file doesn't have a map file. We might be dealing with an older source file from // a plugin or previous version of Moodle so we should just return the full original source @@ -169,10 +159,9 @@ foreach ($jsfiles as $modulename => $jsfile) { $originalsource = str_replace('/amd/build/', '/amd/src/', $jsfile); $originalsource = str_replace('.min.js', '.js', $originalsource); $js = file_get_contents($originalsource); + $js = rtrim($js); } - $js = rtrim($js); - if (preg_match('/define\(\s*\[/', $js)) { // If the JavaScript module has been defined without specifying a name then we'll // add the Moodle module name now. @@ -185,11 +174,8 @@ foreach ($jsfiles as $modulename => $jsfile) { ' module in AMD format. "define()" not found.', DEBUG_DEVELOPER); } - $result[] = $js; + js_send_uncached($js, 'requirejs.php'); +} else { + // We can't find the requested file. + header('HTTP/1.0 404 not found'); } - -$mapdataurl = '//# sourceMappingURL=' . (new \moodle_url('/lib/jssourcemap.php/' . $slashargument))->out(); -$result[] = $mapdataurl; -$content = implode("\n", $result); - -js_send_uncached($content, 'requirejs.php');