babel-plugin-transform-modules-ui5
Version:
An unofficial babel plugin for SAP UI5.
230 lines (218 loc) • 11.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.wrap = wrap;
var _core = require("@babel/core");
var eh = _interopRequireWildcard(require("./exports"));
var th = _interopRequireWildcard(require("../../utils/templates"));
var ast = _interopRequireWildcard(require("../../utils/ast"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function wrap(visitor, programNode, opts) {
var _body;
let {
defaultExport,
exportGlobal,
firstImportMarked,
imports,
namedExports,
ignoredImports,
injectDynamicImportHelper
} = visitor;
const needsWrap = !!(defaultExport || imports.length || namedExports.length || injectDynamicImportHelper);
if (!needsWrap) {
// cleanup the program node if it's empty (just having empty imports)
programNode.body = programNode.body.filter(node => {
if (_core.types.isExportNamedDeclaration(node)) {
return node.declaration != null;
}
return true;
});
return;
}
let {
body
} = programNode;
// find the copyright comment from the original program body and remove it there
// since it need to be put around the new program body (which is sap.ui.define)
let copyright = (_body = body) === null || _body === void 0 || (_body = _body[0]) === null || _body === void 0 || (_body = _body.leadingComments) === null || _body === void 0 ? void 0 : _body.find((comment, idx, arr) => {
if (comment.value.startsWith("!")) {
arr.splice(idx, 1);
return true;
}
});
// in case of TypeScript transpiling taking place upfront, the copyright comment
// is associcated with the program and not the program body, so we need to find it
// and move it to the new program body (which is sap.ui.define)
if (!copyright) {
var _visitor$parent;
copyright = (_visitor$parent = visitor.parent) === null || _visitor$parent === void 0 || (_visitor$parent = _visitor$parent.comments) === null || _visitor$parent === void 0 ? void 0 : _visitor$parent.find((comment, idx, arr) => {
if (comment.value.startsWith("!")) {
arr.splice(idx, 1);
return true;
}
});
}
let allExportHelperAdded = false;
let extendAdded = false;
opts.collapse = !opts.noExportCollapse;
// opts.extend = !opts.noExportExtend
// Before adding anything, see if the named exports can be collapsed into the default export.
if (defaultExport && namedExports.length && opts.collapse) {
let {
filteredExports,
conflictingExports,
newDefaultExportIdentifier
} = eh.collapseNamedExports(programNode, defaultExport, namedExports, opts);
if (filteredExports.length && !opts.allowUnsafeMixedExports) {
throw new Error(`Unsafe mixing of conflicting default and named exports. The following named exports are conflicting: (${ast.getPropNames(conflictingExports).join(", ")}).`);
} else {
namedExports = filteredExports;
}
// The default export may have changed if the collapse logic needed to assign a prop when the default export was previously anonymous.
if (newDefaultExportIdentifier) {
extendAdded = true; // If an anonymous default export needed to be assigned to a a variable, it uses the exports name for convenience.
defaultExport = newDefaultExportIdentifier;
}
}
let moveUseStrictIfNeeded = false;
const preDefine = [...ignoredImports];
// If the noWrapBeforeImport opt is set, split any code before the first import and afterwards into separate arrays.
// This should be done before any interops or other vars are injected.
if (opts.noWrapBeforeImport && firstImportMarked) {
let reachedFirstImport = false;
const fullBody = body;
const newBody = [];
// If there is no lastBeforeWrapping, the first import is not marked
if (!fullBody.find(item => item.lastBeforeWrapping)) {
reachedFirstImport = true;
}
for (const item of fullBody) {
if (reachedFirstImport) {
newBody.push(item);
} else {
preDefine.push(item);
}
if (item.lastBeforeWrapping) {
reachedFirstImport = true;
}
}
moveUseStrictIfNeeded = true;
body = newBody;
}
// If the QUnit.config.autostart is found it needs to be moved to the top of the program
if (opts.noWrapQUnitConfigAutostart === undefined || opts.noWrapQUnitConfigAutostart) {
const qunitConfigAutostart = findQUnitConfigAutostart(body, imports);
if (qunitConfigAutostart) {
preDefine.push(qunitConfigAutostart);
body = body.filter(node => node !== qunitConfigAutostart);
moveUseStrictIfNeeded = true;
}
}
// if code has been moved to preDefine, we need to move the "use strict" directive
if (moveUseStrictIfNeeded) {
if (!opts.neverUseStrict && preDefine.length && !hasUseStrict(programNode)) {
programNode.directives = [_core.types.directive(_core.types.directiveLiteral("use strict")), ...(programNode.directives || [])];
}
}
if (injectDynamicImportHelper) {
// import() to sap.ui.require() w/ promise and interop
body.unshift(th.buildDynamicImportHelper());
}
if (!namedExports.length && defaultExport) {
// If there's no named exports, return the default export
body.push(_core.types.returnStatement(defaultExport));
} else if (namedExports.length) {
if (!extendAdded) {
body.push(th.buildDeclareExports()); // i.e. const __exports = {__esModule: true};
}
for (const namedExport of namedExports) {
if (namedExport.all) {
if (!allExportHelperAdded) {
body.push(th.buildAllExportHelper());
allExportHelperAdded = true;
}
body.push(th.buildAllExport({
LOCAL: namedExport.value
}));
} else {
body.push(th.buildNamedExport(namedExport));
}
}
if (defaultExport) {
body.push(th.buildNamedExport({
key: _core.types.identifier("default"),
value: defaultExport
}));
}
body.push(th.buildReturnExports());
}
if (imports.some(imp => imp.interop)) {
body.unshift(th.buildDefaultImportInterop());
}
// should we use sap.ui.require instead of sap.ui.define?
let useSapUiRequire = hasUseSapUiRequire(visitor.parent.comments, body, true);
// generate the sap.ui.define or sap.ui.require
const defineOrRequire = generateDefineOrRequire(body, imports, exportGlobal || opts.exportAllGlobal, useSapUiRequire);
// add the "use strict" directive if not on program node
if (!opts.neverUseStrict && !hasUseStrict(programNode)) {
const defineOrRequireFnBody = defineOrRequire.expression.arguments[1].body;
defineOrRequireFnBody.directives = [_core.types.directive(_core.types.directiveLiteral("use strict")), ...(defineOrRequireFnBody.directives || [])];
}
programNode.body = [...preDefine, defineOrRequire];
// if a copyright comment is present we append it to the new program node
if (copyright && visitor.parent) {
visitor.parent.leadingComments = visitor.parent.leadingComments || [];
visitor.parent.leadingComments.unshift(copyright);
}
}
function hasUseStrict(node) {
return (node.directives || []).some(directive => directive.value.value === "use strict");
}
function hasUseSapUiRequire(comments, body, remove) {
// detect the @sapUiRequire comment
return comments.some(comment => {
let found = false;
// check for existing comment block
if (comment.type === "CommentBlock") {
found = comment.value.trim() === "@sapUiRequire";
}
// remove the comment (if it is somewhere in the body)
if (found && remove) {
body === null || body === void 0 || body.forEach(node => {
(0, _core.traverse)(node, {
enter(path) {
["leadingComments", "trailingComments", "innerComments"].forEach(key => {
var _path$node$key;
path.node[key] = (_path$node$key = path.node[key]) === null || _path$node$key === void 0 ? void 0 : _path$node$key.filter(c => c !== comment);
});
},
noScope: true
});
});
}
return found;
});
}
function findQUnitConfigAutostart(body, imports) {
// if one imports QUnit, we don't need to move the QUnit.config.autostart
// as the configuration should apply to the local QUnit module
if (imports !== null && imports !== void 0 && imports.some(imp => imp.name === "QUnit")) {
return undefined;
}
// find the QUnit.config.autostart
return body === null || body === void 0 ? void 0 : body.find(node => {
return _core.types.isExpressionStatement(node) && _core.types.isAssignmentExpression(node.expression) && _core.types.isMemberExpression(node.expression.left) && _core.types.isMemberExpression(node.expression.left.object) && _core.types.isIdentifier(node.expression.left.object.object) && node.expression.left.object.object.name === "QUnit" && _core.types.isIdentifier(node.expression.left.object.property) && node.expression.left.object.property.name === "config" && _core.types.isIdentifier(node.expression.left.property) && node.expression.left.property.name === "autostart" && node.expression.operator === "=" /* &&
t.isBooleanLiteral(node.expression.right) &&
node.expression.right.value === false */;
});
}
function generateDefineOrRequire(body, imports, exportGlobal, useRequire) {
const defineOpts = {
SOURCES: _core.types.arrayExpression(imports.map(i => _core.types.stringLiteral(i.src))),
PARAMS: imports.map(i => _core.types.identifier(i.tmpName)),
BODY: body
};
return useRequire ? th.buildRequire(defineOpts) : exportGlobal ? th.buildDefineGlobal(defineOpts) : th.buildDefine(defineOpts);
}