├── .babelrc ├── .gitignore ├── gulpfile.js ├── package.json ├── LICENSE ├── README.md ├── lib └── index.js └── src └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: "es2015" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Dependency directory 24 | node_modules 25 | 26 | # Optional npm cache directory 27 | .npm 28 | 29 | # Optional REPL history 30 | .node_repl_history 31 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const babel = require("gulp-babel"); 2 | const del = require("del"); 3 | const gulp = require("gulp"); 4 | const runSequence = require("run-sequence"); 5 | const watch = require("gulp-watch"); 6 | 7 | const SRC_ROOT = "./src"; 8 | const LIB_ROOT = "./lib"; 9 | 10 | gulp.task("default", [ "build" ]); 11 | gulp.task("dev", [ "build:watch" ]); 12 | 13 | gulp.task("clean", cb => { 14 | del(`${LIB_ROOT}`).then(() => { 15 | cb() 16 | }, reason => { 17 | cb(reason); 18 | }); 19 | }); 20 | 21 | gulp.task("build", [ "clean" ], cb => { 22 | return gulp.src(`${SRC_ROOT}/**/*.js`) 23 | .pipe(babel()) 24 | .pipe(gulp.dest(`${LIB_ROOT}`)); 25 | }); 26 | 27 | gulp.task("build:watch", [ "build" ], cb => { 28 | const jsPath = `${SRC_ROOT}/**/*.js`; 29 | return gulp.src(jsPath) 30 | .pipe(watch(jsPath)) 31 | .pipe(babel()) 32 | .pipe(gulp.dest(`${LIB_ROOT}`)); 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-ui5", 3 | "version": "6.1.1", 4 | "description": "An UNOFFICIAL experimental Babel plugin for SAP UI5.", 5 | "main": "lib/index.js", 6 | "keywords": [ 7 | "ui5", 8 | "sap", 9 | "sapui5", 10 | "openui5", 11 | "babel", 12 | "babeljs", 13 | "babel-plugin", 14 | "babelplugin", 15 | "es6" 16 | ], 17 | "scripts": { 18 | 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/MagicCube/babel-plugin-ui5.git" 23 | }, 24 | "author": "Henry Li ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/MagicCube/babel-plugin-ui5/issues" 28 | }, 29 | "homepage": "https://github.com/MagicCube/babel-plugin-ui5#readme", 30 | "devDependencies": { 31 | "babel-cli": "^6.7.5", 32 | "babel-preset-es2015": "^6.6.0", 33 | "del": "^2.2.0", 34 | "gulp": "^3.9.1", 35 | "gulp-babel": "^6.1.2", 36 | "gulp-watch": "^4.3.5", 37 | "rimraf": "^2.5.2", 38 | "run-sequence": "^1.1.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Henry Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # To get the full project example, please visit [babel-plugin-ui5-example](https://github.com/MagicCube/babel-plugin-ui5-example). 2 | 3 | # babel-plugin-ui5 for Babel 6 4 | An UNOFFICIAL experimental Babel transformer plugin for SAP UI5. 5 | It allows you to develop SAP UI5 applications by using the latest [ES6](http://babeljs.io/docs/learn-es2015/), including new syntax and objective oriented programming technology. 6 | 7 | ## Features 8 | + Imports 9 | + Class, inheritance and `super` keyword 10 | + UI5's `metadata` field 11 | + Static methods and fields 12 | + Most of ES6 features supported by Babel, like arrow functions, spreading, default value of parameters, etc. 13 | + Packed up in a preset named `babel-preset-ui5` or `ui5` for short. 14 | 15 | ## Babel version 16 | Currently this version only supports `Babel 6`. 17 | 18 | If you still want to use Babel 5 in your project, please try my previous project [babel-ui5-plugin](https://github.com/MagicCube/babel-ui5-plugin). 19 | 20 | ## Usage 21 | ### 1. Install the preset 22 | ``` 23 | $ npm install --save-dev babel-preset-ui5 24 | ``` 25 | > `babel-plugin-ui5` require a bunch of plugins including `babel-preset-es2015` and `babel-plugin-syntax-class-properties`. 26 | 27 | > Although you can install `babel-plugin-ui5` and its dependencies directly, 28 | we strongly recommend to install via `babel-preset-ui5`. 29 | 30 | ### 2. Configure .babelrc 31 | Add `ui5` to the `presets`. 32 | ```json 33 | { 34 | "presets": ["ui5"] 35 | } 36 | ``` 37 | 38 | ## Usage with Gulp *(strongly recommended)* 39 | 40 | Suppose that in your project, all the source codes are stored in `src` folder, and all the compiled codes will later be put in `assets` folder. 41 | 42 | ``` 43 | 44 | ├── 45 | ├── 46 | │ └── 47 | │ └── 48 | │ ├── ClassA.js 49 | │ └── ClassB.js 50 | ├── .babelrc 51 | ├── gulpfile.js 52 | └── package.json 53 | ``` 54 | 55 | ### 1. Configure packages.json 56 | Make sure the `babel-preset-ui5` is in your own `package.json`. 57 | ```js 58 | { 59 | ... 60 | "devDependencies": { 61 | "babel-cli": "^6.7.5", 62 | "babel-preset-ui5": "^6", 63 | "del": "^2.2.0", 64 | "gulp": "^3.9.1", 65 | "gulp-babel": "^6.1.2", 66 | "gulp-concat": "^2.6.0", 67 | "gulp-rename": "^1.2.2", 68 | "gulp-uglify": "^1.5.3", 69 | "run-sequence": "^1.1.5" 70 | } 71 | ... 72 | } 73 | ``` 74 | If you don't, please execute the following commands. 75 | ``` 76 | $ npm install --save-dev babel-cli 77 | $ npm install --save-dev del gulp gulp-babel gulp-concat gulp-rename gulp-uglify run-sequence 78 | $ npm install --save-dev babel-preset-ui5 79 | ``` 80 | 81 | ### 2. Configure .babelrc 82 | Add a `.babelrc` in your project root folder. 83 | ```js 84 | { 85 | sourceRoot: "./src", 86 | presets: [ 87 | "ui5" 88 | ] 89 | } 90 | ``` 91 | > The `sourceRoot` property can helps the plugin to output the right namespace. 92 | 93 | ### 3. Configure gulpfile.js 94 | Add a `gulpfile.js` in your project root folder. 95 | ```js 96 | const babel = require("gulp-babel"); 97 | const concat = require("gulp-concat"); 98 | const del = require("del"); 99 | const gulp = require("gulp"); 100 | const rename = require("gulp-rename"); 101 | const runSequence = require("run-sequence"); 102 | const uglify = require("gulp-uglify"); 103 | 104 | const SRC_ROOT = "./src"; 105 | const ASSETS_ROOT = "./assets"; 106 | 107 | gulp.task("default", [ "build" ]); 108 | 109 | gulp.task("clean", cb => { 110 | del(`${ASSETS_ROOT}`).then(() => { 111 | cb() 112 | }, reason => { 113 | cb(reason); 114 | }); 115 | }); 116 | 117 | gulp.task("build", [ "clean" ], cb => { 118 | runSequence( 119 | "build-js", 120 | "concat-js", 121 | "uglify-js" 122 | cb 123 | ); 124 | }); 125 | 126 | gulp.task("build-js", () => { 127 | return gulp.src(`${SRC_ROOT}/**/*.js`) 128 | .pipe(babel()) 129 | .pipe(gulp.dest(`${ASSETS_ROOT}`)); 130 | }); 131 | 132 | gulp.task("concat-js", () => { 133 | return gulp.src(`${ASSETS_ROOT}/**/*.js`) 134 | .pipe(concat("all-dbg.js")) 135 | .pipe(gulp.dest(`${ASSETS_ROOT}`)); 136 | }); 137 | 138 | gulp.task("uglify-js", () => { 139 | return gulp.src(`${ASSETS_ROOT}/all-dbg.js`) 140 | .pipe(uglify()) 141 | .pipe(rename(path => { 142 | path.basename = "all"; 143 | })) 144 | .pipe(gulp.dest(`${ASSETS_ROOT}`)); 145 | }); 146 | ``` 147 | 148 | ### 4. Build with Webpack 149 | Please take a look at [ui5-loader](https://github.com/MagicCube/ui5-loader); 150 | 151 | 152 | ## Modulization 153 | SAP UI5 supports Modulization through a mechanism called `library`. With my another 154 | Gulp plugin [gulp-ui5-lib](https://github.com/MagicCube/gulp-ui5-lib), you're 155 | now able to compile hundreds of JavaScript files into just one library preload 156 | JSON file. 157 | 158 | Please also take a look at [babel-plugin-ui5-example](https://github.com/MagicCube/babel-plugin-ui5-example), 159 | you'll find the answer. 160 | 161 | 162 | ## Example 163 | To get the full project example, please visit [babel-plugin-ui5-example](https://github.com/MagicCube/babel-plugin-ui5-example). 164 | 165 | ### ES6 Codes 166 | ``` javascript 167 | /*---------------------------------* 168 | * File: src/example/obj/Animal.js * 169 | *---------------------------------*/ 170 | 171 | import ManagedObject from "sap/ui/base/ManagedObject"; 172 | 173 | export default class Animal extends ManagedObject 174 | { 175 | metadata: { 176 | properties: { 177 | type: { type: "string" }, 178 | nickName: { type: "string" } 179 | } 180 | } 181 | 182 | constructor(...args) 183 | { 184 | super(...args); 185 | // TODO: Add your own construction code here. 186 | } 187 | 188 | init() 189 | { 190 | // TODO: Add your own initialization code here. 191 | } 192 | 193 | callMe() 194 | { 195 | alert(`I'm a ${this.getType()}. 196 | Call me ${this.getNickName()}.`); 197 | } 198 | } 199 | 200 | 201 | 202 | /*---------------------------------* 203 | * File: src/example/obj/Cat.js * 204 | *---------------------------------*/ 205 | import Animal from "./Animal"; 206 | 207 | export default class Cat extends Animal 208 | { 209 | init() 210 | { 211 | super.init(); 212 | this.setType("Cat"); 213 | } 214 | 215 | callMe() 216 | { 217 | super.callMe(); 218 | alert("Miao~"); 219 | } 220 | 221 | static createCat(nickName) 222 | { 223 | const cat = new example.obj.Cat({ 224 | nickName 225 | }); 226 | return cat; 227 | } 228 | } 229 | ``` 230 | 231 | ## Compiled Codes 232 | ``` javascript 233 | /*------------------------------------* 234 | * File: assets/example/obj/Animal.js * 235 | *------------------------------------*/ 236 | sap.ui.define(["sap/ui/base/ManagedObject"], function (ManagedObject) { 237 | "use strict"; 238 | 239 | return ManagedObject.extend("example.obj.Animal", { 240 | metadata: { 241 | properties: { 242 | type: { type: "string" }, 243 | nickName: { type: "string" } 244 | } 245 | }, 246 | constructor: function constructor() { 247 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 248 | args[_key] = arguments[_key]; 249 | } 250 | 251 | ManagedObject.apply(this, [].concat(args)); 252 | // TODO: Add your own construction code here. 253 | }, 254 | init: function init() { 255 | // TODO: Add your own initialization code here. 256 | }, 257 | callMe: function callMe() { 258 | alert("I'm a " + this.getType() + ". Call me " + this.getNickName() + "."); 259 | } 260 | }); 261 | }); 262 | 263 | 264 | /*---------------------------------* 265 | * File: assets/example/obj/Cat.js * 266 | *---------------------------------*/ 267 | sap.ui.define(["./Animal"], function (Animal) { 268 | "use strict"; 269 | 270 | return Animal.extend("example.obj.Cat", { 271 | init: function init() { 272 | Animal.prototype.init.apply(this, []); 273 | this.setType("Cat"); 274 | }, 275 | callMe: function callMe() { 276 | Animal.prototype.callMe.apply(this, []); 277 | alert("Miao~"); 278 | } 279 | }); 280 | }); 281 | 282 | example.obj.Cat.createCat = function (nickName) { 283 | "use strict"; 284 | 285 | var cat = new example.obj.Cat({ 286 | nickName: nickName 287 | }); 288 | return cat; 289 | }; 290 | 291 | ``` 292 | 293 | ### Full example 294 | To get the full project example, please visit [babel-plugin-ui5-example](https://github.com/MagicCube/babel-plugin-ui5-example). 295 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 4 | 5 | var _path = require("path"); 6 | 7 | var _path2 = _interopRequireDefault(_path); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | exports.default = function (_ref) { 12 | var t = _ref.types; 13 | 14 | var ui5ModuleVisitor = { 15 | Program: { 16 | enter: function enter(path) { 17 | var filePath = _path2.default.resolve(path.hub.file.opts.filename); 18 | 19 | var sourceRootPath = getSourceRoot(path); 20 | 21 | var relativeFilePath = null; 22 | var relativeFilePathWithoutExtension = null; 23 | var namespace = null; 24 | if (filePath.startsWith(sourceRootPath)) { 25 | relativeFilePath = _path2.default.relative(sourceRootPath, filePath); 26 | relativeFilePathWithoutExtension = _path2.default.dirname(relativeFilePath) + _path2.default.sep + _path2.default.basename(relativeFilePath, _path2.default.extname(relativeFilePath)); 27 | relativeFilePathWithoutExtension = relativeFilePathWithoutExtension.replace(/\\/g, "/"); 28 | 29 | var parts = relativeFilePath.split(_path2.default.sep); 30 | if (parts.length <= 1) { 31 | namespace = relativeFilePath; 32 | } else { 33 | parts.pop(); 34 | namespace = parts.join("."); 35 | } 36 | } 37 | 38 | if (!path.state) { 39 | path.state = {}; 40 | } 41 | path.state.ui5 = { 42 | filePath: filePath, 43 | relativeFilePath: relativeFilePath, 44 | relativeFilePathWithoutExtension: relativeFilePathWithoutExtension, 45 | namespace: namespace, 46 | className: null, 47 | fullClassName: null, 48 | superClassName: null, 49 | imports: [], 50 | staticMembers: [] 51 | }; 52 | } 53 | }, 54 | 55 | ImportDeclaration: function ImportDeclaration(path) { 56 | var state = path.state.ui5; 57 | var node = path.node; 58 | var name = null; 59 | 60 | var src = node.source.value; 61 | if (src.startsWith("./") || src.startsWith("../")) { 62 | var sourceRootPath = getSourceRoot(path); 63 | src = _path2.default.relative(sourceRootPath, _path2.default.resolve(_path2.default.dirname(path.hub.file.opts.filename), src)); 64 | } 65 | src = _path2.default.normalize(src); 66 | 67 | if (node.specifiers && node.specifiers.length === 1) { 68 | name = node.specifiers[0].local.name; 69 | } else { 70 | var parts = src.split(_path2.default.sep); 71 | name = parts[parts.length - 1]; 72 | } 73 | 74 | if (node.leadingComments) { 75 | state.leadingComments = node.leadingComments; 76 | } 77 | 78 | var imp = { 79 | name: name, 80 | src: src.replace(/\\/g, "/") 81 | }; 82 | state.imports.push(imp); 83 | 84 | path.remove(); 85 | }, 86 | 87 | ExportDeclaration: function ExportDeclaration(path) { 88 | var state = path.state.ui5; 89 | var program = path.hub.file.ast.program; 90 | 91 | var defineCallArgs = [t.stringLiteral(state.relativeFilePathWithoutExtension), t.arrayExpression(state.imports.map(function (i) { 92 | return t.stringLiteral(i.src); 93 | })), t.functionExpression(null, state.imports.map(function (i) { 94 | return t.identifier(i.name); 95 | }), t.blockStatement([t.expressionStatement(t.stringLiteral("use strict")), t.returnStatement(transformClass(path.node.declaration, program, state))]))]; 96 | var defineCall = t.callExpression(t.identifier("sap.ui.define"), defineCallArgs); 97 | if (state.leadingComments) { 98 | defineCall.leadingComments = state.leadingComments; 99 | } 100 | path.replaceWith(defineCall); 101 | 102 | // Add static members 103 | for (var key in state.staticMembers) { 104 | var id = t.identifier(state.fullClassName + "." + key); 105 | var statement = t.expressionStatement(t.assignmentExpression("=", id, state.staticMembers[key])); 106 | path.insertAfter(statement); 107 | } 108 | }, 109 | 110 | CallExpression: function CallExpression(path) { 111 | var state = path.state.ui5; 112 | var node = path.node; 113 | 114 | if (node.callee.type === "Super") { 115 | if (!state.superClassName) { 116 | this.errorWithNode("The keyword 'super' can only used in a derrived class."); 117 | } 118 | 119 | var identifier = t.identifier(state.superClassName + ".apply"); 120 | var args = t.arrayExpression(node.arguments); 121 | if (node.arguments.length === 1 && node.arguments[0].type === "Identifier" && node.arguments[0].name === "arguments") { 122 | args = t.identifier("arguments"); 123 | } 124 | path.replaceWith(t.callExpression(identifier, [t.identifier("this"), args])); 125 | } else if (node.callee.object && node.callee.object.type === "Super") { 126 | if (!state.superClassName) { 127 | this.errorWithNode("The keyword 'super' can only used in a derrived class."); 128 | } 129 | 130 | var _identifier = t.identifier(state.superClassName + ".prototype" + "." + node.callee.property.name + ".apply"); 131 | path.replaceWith(t.callExpression(_identifier, [t.identifier("this"), t.arrayExpression(node.arguments)])); 132 | } 133 | } 134 | }; 135 | 136 | function transformClass(node, program, state) { 137 | if (node.type !== "ClassDeclaration") { 138 | return node; 139 | } else { 140 | var _ret = function () { 141 | resolveClass(node, state); 142 | 143 | var props = []; 144 | node.body.body.forEach(function (member) { 145 | if (member.type === "ClassMethod") { 146 | var func = t.functionExpression(null, member.params, member.body); 147 | if (!member.static) { 148 | func.generator = member.generator; 149 | func.async = member.async; 150 | props.push(t.objectProperty(member.key, func)); 151 | } else { 152 | func.body.body.unshift(t.expressionStatement(t.stringLiteral("use strict"))); 153 | state.staticMembers[member.key.name] = func; 154 | } 155 | } else if (member.type == "ClassProperty") { 156 | if (!member.static) { 157 | props.push(t.objectProperty(member.key, member.value)); 158 | } else { 159 | state.staticMembers[member.key.name] = member.value; 160 | } 161 | } 162 | }); 163 | 164 | var bodyJSON = t.objectExpression(props); 165 | var extendCallArgs = [t.stringLiteral(state.fullClassName), bodyJSON]; 166 | var extendCall = t.callExpression(t.identifier(state.superClassName + ".extend"), extendCallArgs); 167 | return { 168 | v: extendCall 169 | }; 170 | }(); 171 | 172 | if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v; 173 | } 174 | } 175 | 176 | function resolveClass(node, state) { 177 | state.className = node.id.name; 178 | state.superClassName = node.superClass.name; 179 | if (state.namespace) { 180 | state.fullClassName = state.namespace + "." + state.className; 181 | } else { 182 | state.fullClassName = state.className; 183 | } 184 | } 185 | 186 | function getSourceRoot(path) { 187 | var sourceRootPath = null; 188 | if (path.hub.file.opts.sourceRoot) { 189 | sourceRootPath = _path2.default.resolve(path.hub.file.opts.sourceRoot); 190 | } else { 191 | sourceRootPath = _path2.default.resolve("." + _path2.default.sep); 192 | } 193 | return sourceRootPath; 194 | } 195 | 196 | return { 197 | visitor: ui5ModuleVisitor 198 | }; 199 | }; 200 | module.exports = exports.default; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Path from "path"; 2 | 3 | exports.default = function ({ types: t }) 4 | { 5 | const ui5ModuleVisitor = { 6 | Program: { 7 | enter: path => { 8 | const filePath = Path.resolve(path.hub.file.opts.filename); 9 | 10 | const sourceRootPath = getSourceRoot(path); 11 | 12 | let relativeFilePath = null; 13 | let relativeFilePathWithoutExtension = null; 14 | let namespace = null; 15 | if (filePath.startsWith(sourceRootPath)) 16 | { 17 | relativeFilePath = Path.relative(sourceRootPath, filePath); 18 | relativeFilePathWithoutExtension = Path.dirname(relativeFilePath) + Path.sep + Path.basename(relativeFilePath, Path.extname(relativeFilePath)); 19 | relativeFilePathWithoutExtension = relativeFilePathWithoutExtension.replace(/\\/g, "/"); 20 | 21 | const parts = relativeFilePath.split(Path.sep); 22 | if (parts.length <= 1) 23 | { 24 | namespace = relativeFilePath; 25 | } 26 | else 27 | { 28 | parts.pop(); 29 | namespace = parts.join("."); 30 | } 31 | } 32 | 33 | if (!path.state) 34 | { 35 | path.state = {}; 36 | } 37 | path.state.ui5 = { 38 | filePath, 39 | relativeFilePath, 40 | relativeFilePathWithoutExtension, 41 | namespace, 42 | className: null, 43 | fullClassName: null, 44 | superClassName: null, 45 | imports: [], 46 | staticMembers: [] 47 | }; 48 | } 49 | }, 50 | 51 | 52 | 53 | 54 | 55 | ImportDeclaration: path => { 56 | const state = path.state.ui5; 57 | const node = path.node; 58 | let name = null; 59 | 60 | let src = node.source.value; 61 | if (src.startsWith("./") || src.startsWith("../")) 62 | { 63 | const sourceRootPath = getSourceRoot(path); 64 | src = Path.relative(sourceRootPath, Path.resolve(Path.dirname(path.hub.file.opts.filename), src)); 65 | } 66 | src = Path.normalize(src); 67 | 68 | if (node.specifiers && node.specifiers.length === 1) 69 | { 70 | name = node.specifiers[0].local.name; 71 | } 72 | else 73 | { 74 | const parts = src.split(Path.sep); 75 | name = parts[parts.length - 1]; 76 | } 77 | 78 | if (node.leadingComments) 79 | { 80 | state.leadingComments = node.leadingComments; 81 | } 82 | 83 | const imp = { 84 | name, 85 | src: src.replace(/\\/g, "/") 86 | }; 87 | state.imports.push(imp); 88 | 89 | path.remove(); 90 | }, 91 | 92 | 93 | 94 | 95 | 96 | ExportDeclaration: path => { 97 | const state = path.state.ui5; 98 | const program = path.hub.file.ast.program; 99 | 100 | const defineCallArgs = [ 101 | t.stringLiteral(state.relativeFilePathWithoutExtension), 102 | t.arrayExpression(state.imports.map(i => t.stringLiteral(i.src))), 103 | t.functionExpression(null, state.imports.map(i => t.identifier(i.name)), t.blockStatement([ 104 | t.expressionStatement(t.stringLiteral("use strict")), 105 | t.returnStatement(transformClass(path.node.declaration, program, state)) 106 | ])) 107 | ]; 108 | const defineCall = t.callExpression(t.identifier("sap.ui.define"), defineCallArgs); 109 | if (state.leadingComments) 110 | { 111 | defineCall.leadingComments = state.leadingComments; 112 | } 113 | path.replaceWith(defineCall); 114 | 115 | // Add static members 116 | for (let key in state.staticMembers) 117 | { 118 | const id = t.identifier(state.fullClassName + "." + key); 119 | const statement = t.expressionStatement(t.assignmentExpression("=", id, state.staticMembers[key])); 120 | path.insertAfter(statement); 121 | } 122 | }, 123 | 124 | 125 | 126 | 127 | CallExpression(path) 128 | { 129 | const state = path.state.ui5; 130 | const node = path.node; 131 | 132 | if (node.callee.type === "Super") 133 | { 134 | if (!state.superClassName) 135 | { 136 | this.errorWithNode("The keyword 'super' can only used in a derrived class."); 137 | } 138 | 139 | const identifier = t.identifier(state.superClassName + ".apply"); 140 | let args = t.arrayExpression(node.arguments); 141 | if (node.arguments.length === 1 && node.arguments[0].type === "Identifier" && node.arguments[0].name === "arguments") 142 | { 143 | args = t.identifier("arguments"); 144 | } 145 | path.replaceWith( 146 | t.callExpression(identifier, [ 147 | t.identifier("this"), 148 | args 149 | ]) 150 | ); 151 | } 152 | else if (node.callee.object && node.callee.object.type === "Super") 153 | { 154 | if (!state.superClassName) 155 | { 156 | this.errorWithNode("The keyword 'super' can only used in a derrived class."); 157 | } 158 | 159 | const identifier = t.identifier(state.superClassName + ".prototype" + "." + node.callee.property.name + ".apply"); 160 | path.replaceWith( 161 | t.callExpression(identifier, [ 162 | t.identifier("this"), 163 | t.arrayExpression(node.arguments) 164 | ]) 165 | ); 166 | } 167 | } 168 | }; 169 | 170 | 171 | 172 | function transformClass(node, program, state) 173 | { 174 | if (node.type !== "ClassDeclaration") 175 | { 176 | return node; 177 | } 178 | else 179 | { 180 | resolveClass(node, state); 181 | 182 | const props = []; 183 | node.body.body.forEach(member => { 184 | if (member.type === "ClassMethod") 185 | { 186 | const func = t.functionExpression(null, member.params, member.body); 187 | if (!member.static) 188 | { 189 | func.generator = member.generator; 190 | func.async = member.async; 191 | props.push(t.objectProperty(member.key, func)); 192 | } 193 | else 194 | { 195 | func.body.body.unshift(t.expressionStatement(t.stringLiteral("use strict"))); 196 | state.staticMembers[member.key.name] = func; 197 | } 198 | } 199 | else if (member.type == "ClassProperty") 200 | { 201 | if (!member.static) 202 | { 203 | props.push(t.objectProperty(member.key, member.value)); 204 | } 205 | else 206 | { 207 | state.staticMembers[member.key.name] = member.value; 208 | } 209 | } 210 | }); 211 | 212 | const bodyJSON = t.objectExpression(props); 213 | const extendCallArgs = [ 214 | t.stringLiteral(state.fullClassName), 215 | bodyJSON 216 | ]; 217 | const extendCall = t.callExpression(t.identifier(state.superClassName + ".extend"), extendCallArgs); 218 | return extendCall; 219 | } 220 | } 221 | 222 | 223 | 224 | 225 | 226 | 227 | function resolveClass(node, state) 228 | { 229 | state.className = node.id.name; 230 | state.superClassName = node.superClass.name; 231 | if (state.namespace) 232 | { 233 | state.fullClassName = state.namespace + "." + state.className; 234 | } 235 | else 236 | { 237 | state.fullClassName = state.className; 238 | } 239 | } 240 | 241 | 242 | 243 | function getSourceRoot(path) 244 | { 245 | let sourceRootPath = null; 246 | if (path.hub.file.opts.sourceRoot) 247 | { 248 | sourceRootPath = Path.resolve(path.hub.file.opts.sourceRoot); 249 | } 250 | else 251 | { 252 | sourceRootPath = Path.resolve("." + Path.sep); 253 | } 254 | return sourceRootPath; 255 | } 256 | 257 | 258 | 259 | return { 260 | visitor: ui5ModuleVisitor 261 | }; 262 | }; 263 | module.exports = exports.default; 264 | --------------------------------------------------------------------------------