MDL-62497 javascript: lazy load js modules when cachejs is disabled
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 9 Jul 2019 01:35:22 +0000 (09:35 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Fri, 19 Jul 2019 06:12:49 +0000 (14:12 +0800)
lib/jssourcemap.php
lib/requirejs.php

index 8473fd1..3a1c0d5 100644 (file)
 // 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);
-
-if ($lazyload) {
-    $jsfiles = core_requirejs::find_one_amd_module($component, $module, false);
-} else {
-    $jsfiles = core_requirejs::find_all_amd_modules(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);
 
-// Create the empty source map.
-$map = [
-    'version' => 3,
-    'file' => $CFG->wwwroot . '/lib/requirejs.php/' . $slashargument,
-    'sections' => []
-];
+$mapfile = $jsfile . '.map';
 
-$line = 0;
-// Sort the files to ensure consistent ordering for source map generation.
-asort($jsfiles);
+if (file_exists($mapfile)) {
+    $mapdata = file_get_contents($mapfile);
+    $mapdata = json_decode($mapdata, true);
 
-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');
index 96b4cfc..047fa3f 100644 (file)
@@ -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');