UNPKG

babel-plugin-transform-modules-ui5

Version:
382 lines (373 loc) 16.4 kB
"use strict"; 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); } } } };