@tapcart/app-studio-utils
Version:
A utility library for Tapcart's App Studio
162 lines • 8.03 kB
JavaScript
;
"use client";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.useAppStudioComponents = exports.AppStudioComponentsProvider = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = __importStar(require("react"));
const elements_1 = require("../elements");
const appStudioComponentImportRegex = /import\s+{\s*([^}]+)\s*}\s+from\s+["']@tapcart\/app-studio-components["']/;
const AppStudioComponentsContext = react_1.default.createContext({
appStudioComponents: {},
setComponentScope: () => { },
isReady: false,
});
const AppStudioComponentsProvider = ({ children, componentInputs, scopeInput, }) => {
// Component scope should be set once and then not updated again unless the page state is changed.
// We expose a function for updating the scope manually if needed. (Ex: Server scope vs client scope and we need client scope here)
const [componentScope, setComponentScope] = (0, react_1.useState)(null);
const [scopeOverrides, setScopeOverrides] = (0, react_1.useState)({});
const [isPending, startTransition] = (0, react_1.useTransition)();
const scopeHasChanges = (0, react_1.useCallback)((oldScope, newScope) => {
// Remove page state from comparison
// Page state is static except for the search params - search params are delivered through useSearchParams hook
// Do not need to regenerate components if only search params have changed. Also provides better user experience
// Copy objects first to prevent unintended mutations of the original scope objects
const scope1 = Object.assign({}, oldScope);
const scope2 = Object.assign({}, newScope);
delete scope1.pageState;
delete scope2.pageState;
// JSON.stringify will remove undefined values and functions
// Compare the number of keys to determine if the scope has changed
if (Object.keys(scope1).length !== Object.keys(scope2).length) {
return true;
}
return JSON.stringify(scope1) !== JSON.stringify(scope2);
}, []);
(0, react_1.useEffect)(() => {
const currentScope = componentScope || scopeInput;
if (!currentScope)
return;
let newScope = Object.assign({}, currentScope);
// Remove block specific properties from scope overrides as this is not a block
delete newScope.blockConfig;
delete newScope.blockId;
// TODO: Implement manifest options
newScope.componentConfig = {};
// Check if the scope has changed
if (!scopeHasChanges(scopeOverrides, newScope))
return;
startTransition(() => {
setScopeOverrides(newScope);
});
}, [componentScope, scopeInput]);
// Responsible for instantiating a component and its dependencies
const instantiateComponent = (0, react_1.useCallback)((component, componentCache) => {
// Components can reference each other. Find app studio component dependencies in component code
const importMatch = component.code.match(appStudioComponentImportRegex);
// If there is an import, there are other custom component dependencies that we need to load
if (importMatch === null || importMatch === void 0 ? void 0 : importMatch.length) {
// Extract the component dependencies from the import statement
const strippedImport = importMatch[0]
.replace(/import {|}|"|;|from|@tapcart\/app-studio-components/g, "")
.trim();
const componentDependencies = strippedImport
.split(",")
.map((dep) => dep.trim());
// For each dependency, create that component and store it in the cache
for (const dependency of componentDependencies) {
if (!dependency || componentCache[dependency])
continue;
// Find dependency in componentInputs
const dependencyComponent = componentInputs[dependency];
if (!dependencyComponent) {
console.warn(`App studio component "${dependency}" not found in componentInputs`);
continue;
}
const dependencyComponentInstance = instantiateComponent(dependencyComponent, componentCache);
if (!dependencyComponentInstance)
continue;
componentCache[dependency] = dependencyComponentInstance;
}
}
// With dependencies loaded, instantiate the component
const AppStudioComponent = (0, elements_1.generateElement)({
code: component.transpiledCode,
scopeOverrides,
appStudioComponents: componentCache,
});
if (!AppStudioComponent) {
console.error(`Failed to instantiate app studio component ${component.key}`);
return null;
}
// Return function that renders the component. Do this so we can inject our scope as well as block related props.
const Component = (props) => ((0, jsx_runtime_1.jsx)(AppStudioComponent, Object.assign({}, scopeOverrides, props)));
Component.displayName = "AppStudioComponent";
return Component;
}, [scopeOverrides]);
// Full output of instantiated components
const appStudioComponents = (0, react_1.useMemo)(() => {
if (isPending)
return {};
let componentCache = {};
// Turn app studio component inputs into a map of component name to component function
const components = Object.keys(componentInputs).reduce((acc, key) => {
if (!componentInputs[key])
return acc;
const component = instantiateComponent(componentInputs[key], componentCache);
if (!component)
return acc;
acc[key] = component;
return acc;
}, {});
return components;
}, [scopeOverrides, componentInputs]);
return ((0, jsx_runtime_1.jsx)(AppStudioComponentsContext.Provider, { value: {
appStudioComponents,
setComponentScope,
isReady: !isPending,
}, children: children }));
};
exports.AppStudioComponentsProvider = AppStudioComponentsProvider;
const useAppStudioComponents = () => {
const context = react_1.default.useContext(AppStudioComponentsContext);
if (context === undefined) {
throw new Error("useAppStudioComponents must be used within a AppStudioComponentsProvider");
}
return context;
};
exports.useAppStudioComponents = useAppStudioComponents;
//# sourceMappingURL=index.js.map