babel-plugin-transform-modules-ui5
Version:
An unofficial babel plugin for SAP UI5.
382 lines (373 loc) • 16.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ModuleTransformVisitor = void 0;
var _path = require("path");
var _fs = require("fs");
var _core = require("@babel/core");
var th = _interopRequireWildcard(require("../utils/templates"));
var ast = _interopRequireWildcard(require("../utils/ast"));
var _jsdoc = require("../classes/helpers/jsdoc");
const _excluded = ["filename", "opts"];
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
const resolveSource = (src, filename) => {
src = src.replace(/\\/g, "/");
const dir = (0, _path.dirname)(filename);
const absoluteSrc = (0, _path.join)(dir, src);
if ((0, _fs.existsSync)(absoluteSrc) && (0, _fs.statSync)(absoluteSrc).isDirectory) {
if ((0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.js")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.jsx")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.ts")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.tsx")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.mjs")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.cjs"))) {
src = `${src}/index`;
}
}
return src;
};
const cleanImportSource = src => src.replace(/(\/)|(-)|(@)/g, "_").replace(/\./g, "");
const tempModuleName = name => `__${name}`;
const hasGlobalExportFlag = node => (0, _jsdoc.hasJsdocGlobalExportFlag)(node);
const addImport = (imports, imp, filename, first) => {
const existingImport = imports.find(i => i.src === imp.src);
if (!existingImport) {
// if a module path ends with the file extension ".js" and it can be resolved to
// a local file having also the file extension ".js" and not ".js.js" then
// we need to slice the file extension to avoid redundant file extension
// (the require/define of UI5 always adds the file extension ".js" to the module name)
if (/^(?:(@[^/]+)\/)?([^/]+)\/(.*)\.js$/.test(imp.src)) {
try {
let modulePath;
let absModuleSrc = imp.src;
// if the module has a relative path, resolve it to an absolute path
// and verify if the file exists, if not, try to resolve it as a module
if (/^\.\.?\//.test(absModuleSrc)) {
absModuleSrc = (0, _path.resolve)((0, _path.dirname)(filename), absModuleSrc);
// handle the fallback of module names introduced with Node 20.0.0
[".js", ".jsx", ".ts", ".tsx"].some(ext => {
if ((0, _fs.existsSync)(absModuleSrc.replace(/\.js$/, ext))) {
modulePath = imp.src;
return true;
}
});
} else {
modulePath = require.resolve(absModuleSrc);
}
// detect removal of file extension and log a hint
if (modulePath.endsWith(imp.src.split("/").pop())) {
console.log(`\x1b[33mHint:\x1b[0m Removed file extension for dependency "\x1b[34m${imp.src}\x1b[0m" found in \x1b[90m${filename}\x1b[0m`);
imp.src = imp.src.slice(0, -3);
}
} catch (ex) {
// ignore the error, do not slice file extension as the module
// can't be resolved with the given file extension, e.g.
// myns/module.js must be provided as myns/module
// myns/module.js.js must be provided as myns/module.js
}
}
imports[first ? "unshift" : "push"](imp);
}
};
const addModuleImport = (imports, name, filename) => {
addImport(imports, {
src: name,
name: name,
tmpName: name
}, filename, true);
};
const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
/*!
* Removes the ES6 import and adds the details to the import array in our state.
*/
ImportDeclaration(path, _ref) {
let {
filename,
opts = {}
} = _ref,
state = _objectWithoutProperties(_ref, _excluded);
const {
node
} = path;
if (node.importKind === "type") return; // flow-type
const {
specifiers,
source
} = node;
const src = resolveSource(source.value, filename);
// When 'libs' are used, only 'libs' will be converted to UI5 imports.
const {
libs = [".*"]
} = opts;
const isLibToConvert = new RegExp(`(${libs.join("|")})`).test(src);
if (!isLibToConvert) {
this.ignoredImports.push(node);
path.remove();
return;
}
//const testSrc = (opts.libs || ["^sap/"]).concat(opts.files || []);
// const isUi5SrcRE = testSrc.length && new RegExp(`(${testSrc.join("|")})`);
// const isUi5Src = isUi5SrcRE.test(src);
// Importing using an interop is the default behaviour but can be opt-out using regex.
const shouldInterop = !this.noImportInteropPrefixesRegexp.test(src);
const name = cleanImportSource(src); // default to the src for import without named var
const {
modulesMap = {}
} = opts;
const mappedSrc = (typeof modulesMap === "function" ? modulesMap(src, {
node,
opts,
cwd: state.cwd,
filename: state.filename,
file: {
opts: state.file.opts
}
}) : modulesMap[src]) || src;
// Note that existingImport may get mutated if there are multiple import lines from the same module.
const existingImport = this.imports.find(imp => imp.src === mappedSrc);
const imp = existingImport || {
src: mappedSrc,
// url
name,
// isLib, // for future use separating UI5 imports from npm/webpack imports
// isUi5Src, // not used yet
tmpName: shouldInterop ? tempModuleName(name) : name,
deconstructors: [],
default: false,
interop: false,
path: path,
locked: false
};
const deconstructors = [];
for (const specifier of specifiers) {
if (_core.types.isImportDefaultSpecifier(specifier)) {
// e.g. import X from 'X'
imp.default = true;
imp.interop = shouldInterop;
// Shorten the imported-as name since it should be unique for default imports.
// The default import should always come first,
// so this new name will be used for destructuring the other too.
if (!imp.locked) {
imp.name = specifier.local.name;
imp.tmpName = shouldInterop ? tempModuleName(imp.name) : imp.name;
imp.locked = true;
}
if (shouldInterop) {
deconstructors.push(th.buildDefaultImportDeconstructor({
MODULE: _core.types.identifier(imp.tmpName),
LOCAL: specifier.local
}));
}
} else if (_core.types.isImportNamespaceSpecifier(specifier)) {
if (specifiers.length === 1 && !imp.locked) {
// e.g. import * as X from 'X'
// If the namespace specifier is the only import, we can avoid the temp name and the destructor
imp.name = specifier.local.name;
imp.tmpName = specifier.local.name;
imp.locked = true; // Don't let another import line for the same module change the name.
} else {
// e.g. import X, * as X2 from 'X'
// Else it's probably combined with a default export. keep the tmpName and destructure it
deconstructors.push(th.buildConstDeclaration({
NAME: specifier.local,
VALUE: _core.types.identifier(imp.tmpName)
}));
}
} else if (_core.types.isImportSpecifier(specifier)) {
// e.g. import { A } from 'X'
deconstructors.push(th.buildNamedImportDestructor({
MODULE: _core.types.identifier(imp.tmpName),
LOCAL: specifier.local,
IMPORTED: _core.types.stringLiteral(specifier.imported.name)
}));
} else {
throw path.buildCodeFrameError(`Unknown ImportDeclaration specifier type ${specifier.type}`);
}
}
// this is the very first import in noWrapBeforeImport mode and there are sibling nodes before this import
if (opts.noWrapBeforeImport && !this.firstImportMarked && path.inList) {
// for the first element there is a special case as we can't mark the previous one
// therefore we do not mark anything to make clear that there's nothing to exclude
if (path.key > 0) {
// mark the direct predecessor as the last one to exclude from wrapping
path.getSibling(path.key - 1).node.lastBeforeWrapping = true;
}
this.firstImportMarked = true;
}
path.replaceWithMultiple(deconstructors);
if (deconstructors.length) {
// Keep the same variable name if the same module is imported on another line.
imp.locked = true;
}
imp.deconstructors = imp.deconstructors.concat(deconstructors);
if (!existingImport) {
addImport(this.imports, imp, filename);
}
},
/**
* Push all exports to an array.
* The reason we don't export in place is to handle the situation
* where a let or var can be defined, and the latest one should be exported.
*/
ExportNamedDeclaration(path, {
filename
}) {
const {
node
} = path;
const {
specifiers,
declaration,
source
} = node;
let fromSource = "";
if (source) {
// e.g. export { one, two } from 'x'
const src = resolveSource(source.value, filename);
const name = cleanImportSource(src);
const tmpName = tempModuleName(name);
addImport(this.imports, {
src,
name,
tmpName
}, filename);
fromSource = tmpName + ".";
}
if (specifiers && specifiers.length) {
// e.g. export { one, two }
for (const specifier of path.node.specifiers) {
this.namedExports.push({
key: specifier.exported,
value: _core.types.identifier(`${fromSource}${specifier.local.name}`)
});
}
path.remove();
} else if (declaration) {
// e.g. export const c = 1 | export function f() {}
if (["TypeAlias", "InterfaceDeclaration", "TSInterfaceDeclaration", "TSTypeAliasDeclaration"].includes(declaration.type)) return; // TS or Flow-types
const name = ast.getIdName(declaration);
if (name) {
// e.g. export function f() {}
const id = _core.types.identifier(declaration.id.name);
this.namedExports.push({
key: id,
value: id,
declaration
});
} else if (declaration.declarations) {
// e.g. export const c = 1
for (const subDeclaration of declaration.declarations) {
const id = _core.types.identifier(subDeclaration.id.name);
this.namedExports.push({
value: id,
key: id,
declaration: subDeclaration
});
}
} else {
throw path.buildCodeFrameError("Unknown ExportNamedDeclaration shape.");
}
path.replaceWith(declaration);
} else {
path.remove();
return;
}
},
ExportDefaultDeclaration(path /*, { filename } */) {
const {
node
} = path;
let {
declaration
} = node;
const declarationName = ast.getIdName(declaration);
if (hasGlobalExportFlag(node)) {
// check for jsdoc @export
this.exportGlobal = true;
}
if (declarationName) {
// ClassDeclaration or FunctionDeclaration with name.
// Leave the declaration in-line and preserve the identifier for the return statement.
path.replaceWith(declaration);
this.defaultExport = _core.types.identifier(declarationName);
} else if (_core.types.isIdentifier(declaration)) {
this.defaultExport = declaration;
path.remove();
} else {
// anonymous ObjectExpression or anonymous FunctionDeclaration
if (_core.types.isFunctionDeclaration(declaration)) {
const {
params,
body,
generator,
async: isAsync
} = declaration;
declaration = _core.types.functionExpression(null, params, body, generator, isAsync);
}
const exportDeclaration = th.buildTempExport({
VALUE: declaration
});
path.replaceWith(exportDeclaration);
this.defaultExport = th.exportsIdentifier;
}
},
ExportAllDeclaration(path, {
filename
}) {
const src = resolveSource(path.node.source.value, filename);
const name = cleanImportSource(src);
const tmpName = tempModuleName(name);
addImport(this.imports, {
src,
name,
tmpName
}, filename);
this.exportAllHelper = true;
this.namedExports.push({
all: true,
value: _core.types.identifier(tmpName)
});
path.remove();
},
/*!
* Visits function calls to handle for dynamic imports.
*/
CallExpression(path) {
const {
node
} = path;
const {
callee
} = node;
if (ast.isImport(callee)) {
this.injectDynamicImportHelper = true;
path.replaceWith(_objectSpread(_objectSpread({}, node), {}, {
callee: _core.types.identifier("__ui5_require_async")
}));
}
},
MemberExpression(path, {
filename
}) {
var _node$object;
const {
node
} = path;
if ((node === null || node === void 0 || (_node$object = node.object) === null || _node$object === void 0 ? void 0 : _node$object.type) === "MetaProperty") {
var _node$property, _node$property2;
// replace all "import.meta.url" with "module.url"
if ((node === null || node === void 0 || (_node$property = node.property) === null || _node$property === void 0 ? void 0 : _node$property.name) === "url") {
path.replaceWith(_objectSpread(_objectSpread({}, node), (0, _core.template)`module.url`()));
addModuleImport(this.imports, "module", filename);
}
// replace all "import.meta.resolve(...)" with "require.toUrl(...)"
else if ((node === null || node === void 0 || (_node$property2 = node.property) === null || _node$property2 === void 0 ? void 0 : _node$property2.name) === "resolve") {
path.replaceWith(_objectSpread(_objectSpread({}, node), (0, _core.template)`require.toUrl`()));
addModuleImport(this.imports, "require", filename);
}
}
}
};
;