How a Single Input Box Can Power Unlimited Backend Configurations

This article explains a flexible solution that lets backend administrators write JavaScript functions directly in an input box, using parameter interpolation and a sandboxed Node.js VM to dynamically generate HTML, CSS, or script tags, handling complex logic, data transformation, and conditional branching safely.

AutoHome Frontend
AutoHome Frontend
AutoHome Frontend
How a Single Input Box Can Power Unlimited Backend Configurations

Why Write Functions Directly in an Input Box?

Traditional form‑based configuration works for simple cases but quickly fails when dealing with dynamic logic, complex data transformations, conditional branches, or inter‑dependent parameters. Embedding a JavaScript function in the input field gives unlimited flexibility, dynamic execution, reusable logic, and easy debugging.

Defining a Function Input Box

We expose a special input named output. Users write a JavaScript function that receives config, option, and optionally query. The function returns an array of objects, each describing an HTML tag to be inserted into the page.

Component object fields : type: tag type (e.g., script, style, link, meta) name: a human‑readable identifier attrs: an array of attribute objects

{name, value}
content

: the inner HTML or script content

Basic Example

async function output(config, option, query) {
  const content = await fetchData({
    url: config.url,
  });

  return [{
    type: 'script',
    name: 'example-component',
    attrs: [{ name: 'type', value: 'text/javascript' }],
    content: content,
  }];
}

Security: Sandbox Isolation

To execute user‑provided code safely we create a restricted sandbox using Node.js's vm module.

import vm from 'node:vm';

// Create a limited sandbox context
const sandbox = {
  _,               // lodash utilities
  console,
  fetchData,
  transformCustomVariables,
  URL,
};
const context = vm.createContext(sandbox);

The sandbox only exposes the APIs listed above, preventing access to the host system.

Execution Flow

const handleTemplate = async (userFunctionString, config, option) => {
  try {
    // 1️⃣ Parameter interpolation
    const replacedUserFunctionString = replaceInterpolate(userFunctionString, { config, option });

    // 2️⃣ Run the user code inside the sandbox
    const userFunction = vm.runInContext(`(${replacedUserFunctionString})`, context);

    // 3️⃣ Execute the function and get the raw result
    const result = await userFunction(config, option);

    // 4️⃣ Post‑process (e.g., minify) each returned item
    const processedResult = result.map(item => ({
      ...item,
      content: uglifyCode(item.content),
    }));

    return processedResult;
  } catch (error) {
    throw new Error(`函数执行错误: ${error.message}`);
  }
};

Parameter Interpolation Mechanism

Users can reference config and option values inside their function using the {{}} syntax. The interpolation process first parses the raw input values, then replaces the placeholders with actual JavaScript expressions.

function replaceInterpolate(template, { config, option }) {
  const reg = /\{\{\s*(config\.[^}]+|option\.[^}]+)\s*\}\}/g;
  if (!reg.test(template)) return template;
  return _.template(template, { interpolate: reg })({
    config: formatParams(config),
    option: formatParams(option),
  });
}

Helper utilities:

// Convert a value field from string to real JS value
function parseValueField(obj) {
  return Object.entries(obj).reduce((acc, [key, valueObj]) => {
    if (['object','array','arrayByItems','boolean','function','number'].includes(valueObj.type)) {
      try {
        valueObj.value = evalStrToObj(valueObj.value);
        acc[key] = valueObj;
      } catch (e) {
        console.error(`解析 ${key} 的值失败: `, e);
      }
    } else {
      acc[key] = valueObj;
    }
    return acc;
  }, {});
}

// Turn a string into a JS expression safely
const evalStrToObj = (str) => new Function(`return (${str})`)();

// Recursively format parameters for interpolation
function formatParams(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;
  return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, stringifyValueAsExpression(v)]));
}

// Convert any JS value to a valid JS literal string
function stringifyValueAsExpression(value) {
  if (value instanceof RegExp) return value.toString();
  if (Array.isArray(value)) return `[${value.map(stringifyValueAsExpression).join(',')}]`;
  if (typeof value === 'object' && value !== null) {
    return `{${Object.entries(value).map(([k, v]) => `${k}:${stringifyValueAsExpression(v)}`).join(',')}}`;
  }
  if (typeof value === 'string') return `'${value.replace(/'/g, "\\'")}'`;
  if (value == null) return 'null';
  return String(value);
}

Interpolation Example

function output(config, option) {
  const theme = '{{config.theme}}';
  const buttonColor = '{{option.buttonColor}}';
  const features = {{option.features}};
  return [{
    type: 'style',
    name: 'dynamic-style',
    attrs: [{ name: 'type', value: 'text/css' }],
    content: `
      .container {
        theme: ${theme};
        --button-color: ${buttonColor};
      }
      .features::after {
        content: '${features.join(', ')}';
      }
    `,
  }];
}

After interpolation with

option = { buttonColor: 'blue', features: ['login','register'], config: { theme: 'dark' } }

the function becomes:

function output(config, option, query) {
  const color = 'blue';
  const features = ['login','register'];
  const timeout = 5000;
  return [{
    type: 'script',
    name: 'debug-info',
    attrs: [{ name: 'type', value: 'text/javascript' }],
    content: `
      console.log('颜色:blue');
      console.log('功能:', ["login","register"]);
      console.log('超时:5000ms');
    `,
  }];
}

Real‑World Cases

Case 1 – Conditional Font Management Component

This component generates @font-face rules, CSS variables, and utility classes based on which fonts the user enables in option.

async function output(config, option) {
  const { 'custom-regular': enableRegular, 'custom-medium': enableMedium, 'custom-bold': enableBold } = option;
  const fontList = [
    { name: 'custom-regular', content: '@font-face { font-family: custom-regular; src: url("https://cdn.example.com/fonts/CustomFont_Regular.woff2") format("woff2"); font-weight: 400; font-display: swap; }' },
    { name: 'custom-medium',  content: '@font-face { font-family: custom-medium; src: url("https://cdn.example.com/fonts/CustomFont_Medium.woff2") format("woff2"); font-weight: 500; font-display: swap; }' },
    { name: 'custom-bold',    content: '@font-face { font-family: custom-bold; src: url("https://cdn.example.com/fonts/CustomFont_Bold.woff2") format("woff2"); font-weight: 700; font-display: swap; }' },
  ];
  const activeKeys = Object.keys(option).filter(k => option[k]);
  const filterList = fontList.filter(item => activeKeys.includes(item.name));
  const content = _ .template(`
    <% _.forEach(list, function(item) { %>
      <%= item.content %>
    <% }); %>
    :root {
      <% _.forEach(list, function(item) { var clean = item.name.replace('custom-',''); %>
        --font-<%= clean %>: custom-<%= clean %>;
      <% }); %>
    }
    <% _.forEach(list, function(item) { var clean = item.name.replace('custom-',''); %>
      .f-<%= clean.split('-').map(w=>w[0]).join('') %> { font-family: var(--font-<%= clean %>); }
    <% }); %>
  `)({ list: filterList });
  return [{
    type: 'style',
    name: '字体管理 CSS 变量版',
    attrs: [{ name: 'type', value: 'text/css' }],
    content,
  }];
}

Case 2 – Page Grayscale Component

async function output(config, option, query) {
  const customCss = option.customCss || '';
  return [{
    type: 'script',
    name: '网页置灰',
    attrs: [],
    content: `
      (function () {
        let grayscaleStyleElement = null;
        let intervalId = null;
        function enableGrayscale() {
          if (grayscaleStyleElement) return;
          const style = document.createElement('style');
          style.innerHTML = `html {filter: grayscale(0.95);-webkit-filter: grayscale(0.95);}` + ${customCss};
          document.documentElement.insertBefore(style, document.head);
          grayscaleStyleElement = style;
        }
        function disableGrayscale() {
          if (grayscaleStyleElement) {
            document.documentElement.removeChild(grayscaleStyleElement);
            grayscaleStyleElement = null;
          }
        }
        function getMillTime(d) { return d ? new Date(d).getTime() : Date.now(); }
        function handleGrayscale(start, end) {
          const now = getMillTime();
          if (now >= start && now <= end) enableGrayscale(); else disableGrayscale();
          if (now > end && !grayscaleStyleElement) { clearInterval(intervalId); intervalId = null; }
        }
        function scheduleGrayscaleEffect() {
          const start = getMillTime({{option.startDate}});
          const end   = getMillTime({{option.endDate}});
          handleGrayscale(start, end);
          intervalId = setInterval(() => handleGrayscale(start, end), 10000);
        }
        scheduleGrayscaleEffect();
      })();
    `,
  }];
}

Conclusion

Embedding a JavaScript function in a configuration input box provides:

Extreme flexibility : users can encode any business logic.

Full expressive power : all JavaScript features (conditionals, loops, async calls) are available.

Secure execution : a sandbox isolates user code from the main process.

Rich parameter support : primitive, array, object, and even function types can be interpolated.

Easy extensibility : new APIs can be added to the sandbox without breaking existing functions.

While the approach requires users to know basic JavaScript, the payoff for complex backend scenarios is substantial. Future enhancements may include a template library, visual debugging tools, and version control for user‑written functions.

Function input box UI
Function input box UI
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

sandboxdynamic-configparameter-interpolation
AutoHome Frontend
Written by

AutoHome Frontend

AutoHome Frontend Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.