| [ Index ] |
PHP Cross Reference of Moodle 310 |
[Summary view] [Print] [Text view]
1 // This file is part of Moodle - http://moodle.org/ 2 // 3 // Moodle is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // Moodle is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 15 16 /** 17 * This is a babel plugin to add the Moodle module names to the AMD modules 18 * as part of the transpiling process. 19 * 20 * In addition it will also add a return statement for the default export if the 21 * module is using default exports. This is a highly specific Moodle thing because 22 * we're transpiling to AMD and none of the existing Babel 7 plugins work correctly. 23 * 24 * This will fix the issue where an ES6 module using "export default Foo" will be 25 * transpiled into an AMD module that returns {default: Foo}; Instead it will now 26 * just simply return Foo. 27 * 28 * Note: This means all other named exports in that module are ignored and won't be 29 * exported. 30 * 31 * @copyright 2018 Ryan Wyllie <ryan@moodle.com> 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 35 "use strict"; 36 /* eslint-env node */ 37 38 module.exports = ({template, types}) => { 39 const fs = require('fs'); 40 const path = require('path'); 41 const cwd = process.cwd(); 42 const ComponentList = require(path.resolve('GruntfileComponents.js')); 43 44 /** 45 * Search the list of components that match the given file name 46 * and return the Moodle component for that file, if found. 47 * 48 * Throw an exception if no matching component is found. 49 * 50 * @throws {Error} 51 * @param {string} searchFileName The file name to look for. 52 * @return {string} Moodle component 53 */ 54 function getModuleNameFromFileName(searchFileName) { 55 searchFileName = fs.realpathSync(searchFileName); 56 const relativeFileName = searchFileName.replace(`$cwd}$path.sep}`, '').replace(/\\/g, '/'); 57 const [componentPath, file] = relativeFileName.split('/amd/src/'); 58 const fileName = file.replace('.js', ''); 59 60 // Check subsystems first which require an exact match. 61 const componentName = ComponentList.getComponentFromPath(componentPath); 62 if (componentName) { 63 return `$componentName}/$fileName}`; 64 } 65 66 // This matches the previous PHP behaviour that would throw an exception 67 // if it couldn't parse an AMD file. 68 throw new Error(`Unable to find module name for $searchFileName} ($componentPath}::$file}}`); 69 } 70 71 /** 72 * This is heavily inspired by the babel-plugin-add-module-exports plugin. 73 * See: https://github.com/59naga/babel-plugin-add-module-exports 74 * 75 * This is used when we detect a module using "export default Foo;" to make 76 * sure the transpiled code just returns Foo directly rather than an object 77 * with the default property (i.e. {default: Foo}). 78 * 79 * Note: This means that we can't support modules that combine named exports 80 * with a default export. 81 * 82 * @param {String} path 83 * @param {String} exportObjectName 84 */ 85 function addModuleExportsDefaults(path, exportObjectName) { 86 const rootPath = path.findParent(path => { 87 return path.key === 'body' || !path.parentPath; 88 }); 89 90 // HACK: `path.node.body.push` instead of path.pushContainer(due doesn't work in Plugin.post). 91 // This is hardcoded to work specifically with AMD. 92 rootPath.node.body.push(template(`return $exportObjectName}.default`)()); 93 } 94 95 return { 96 pre() { 97 this.seenDefine = false; 98 this.addedReturnForDefaultExport = false; 99 }, 100 visitor: { 101 // Plugin ordering is only respected if we visit the "Program" node. 102 // See: https://babeljs.io/docs/en/plugins.html#plugin-preset-ordering 103 // 104 // We require this to run after the other AMD module transformation so 105 // let's visit the "Program" node. 106 Program: { 107 exit(path) { 108 path.traverse({ 109 CallExpression(path) { 110 // If we find a "define" function call. 111 if (!this.seenDefine && path.get('callee').isIdentifier({name: 'define'})) { 112 // We only want to modify the first instance of define that we find. 113 this.seenDefine = true; 114 115 // Get the Moodle component for the file being processed. 116 var moduleName = getModuleNameFromFileName(this.file.opts.filename); 117 118 // The function signature of `define()` is: 119 // define = function (name, deps, callback) {...} 120 // Ensure that if the moduel supplied its own name that it is replaced. 121 if (path.node.arguments.length > 0) { 122 // Ensure that there is only one name. 123 if (path.node.arguments[0].type === 'StringLiteral') { 124 // eslint-disable-next-line 125 console.log(`Replacing module name '$path.node.arguments[0].extra.rawValue}' with $moduleName}`); 126 path.node.arguments.shift(); 127 } 128 } 129 130 // Add the module name as the first argument to the define function. 131 path.node.arguments.unshift(types.stringLiteral(moduleName)); 132 // Add a space after the define function in the built file so that previous versions 133 // of Moodle will not try to add the module name to the file when it's being served 134 // by PHP. This forces the regex in PHP to not match for this file. 135 path.node.callee.name = 'define '; 136 } 137 138 // Check for any Object.defineProperty('exports', 'default') calls. 139 if (!this.addedReturnForDefaultExport && path.get('callee').matchesPattern('Object.defineProperty')) { 140 const [identifier, prop] = path.get('arguments'); 141 const objectName = identifier.get('name').node; 142 const propertyName = prop.get('value').node; 143 144 if ((objectName === 'exports' || objectName === '_exports') && propertyName === 'default') { 145 addModuleExportsDefaults(path, objectName); 146 this.addedReturnForDefaultExport = true; 147 } 148 } 149 }, 150 AssignmentExpression(path) { 151 // Check for an exports.default assignments. 152 if ( 153 !this.addedReturnForDefaultExport && 154 ( 155 path.get('left').matchesPattern('exports.default') || 156 path.get('left').matchesPattern('_exports.default') 157 ) 158 ) { 159 const objectName = path.get('left.object.name').node; 160 addModuleExportsDefaults(path, objectName); 161 this.addedReturnForDefaultExport = true; 162 } 163 } 164 }, this); 165 } 166 } 167 } 168 }; 169 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 22 11:59:49 2025 | Cross-referenced by PHPXref 0.7.1 |