
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
rw-elements-tools
Advanced tools
Build tools for RapidWeaver Elements packs - generates properties.json and hooks.js files
The official build toolkit for creating RapidWeaver Elements
Build powerful, reusable web components for RapidWeaver without the complexity. This toolkit handles the heavy lifting so you can focus on what matters: creating great elements.
rw-elements-tools is a development toolkit that simplifies the process of creating custom elements for RapidWeaver, the popular Mac website builder. It provides:
| Without rw-elements-tools | With rw-elements-tools |
|---|---|
| Manually write complex JSON config files | Use intuitive JavaScript configuration |
| Copy/paste utility code between elements | Import from a shared library |
| Bloated output with unused code | Automatic dead code elimination |
| Manual rebuilds on every change | Watch mode for instant updates |
npm install --save-dev rw-elements-tools
Create a new directory for your element pack project and initialize it:
mkdir my-element-pack
cd my-element-pack
npm init -y
npm install --save-dev rw-elements-tools
Your project needs a packs folder containing your element pack(s). Each pack follows this structure:
my-element-pack/
├── package.json
├── packs/ # Default packs directory
│ └── MyPack.elementsdevpack/ # Your pack (must end in .elementsdevpack)
│ └── components/
│ └── com.yourcompany.elementname/ # Component folder (must start with com.)
│ ├── properties.config.json # Source config (you edit this)
│ ├── properties.json # Generated output (don't edit)
│ ├── hooks.source.js # Source hooks (you edit this)
│ └── hooks.js # Generated output (don't edit)
└── node_modules/
Key naming conventions:
.elementsdevpackcom. (e.g., com.mycompany.button)properties.config.json and hooks.source.jsproperties.json and hooks.jsCreate the folder structure for your first element:
mkdir -p packs/MyPack.elementsdevpack/components/com.mycompany.button
Create a minimal properties.config.json:
{
"groups": [
{
"title": "Content",
"icon": "text.alignleft",
"properties": [
{
"title": "Button Text",
"id": "buttonText",
"text": {
"default": "Click Me"
}
}
]
}
]
}
Create a hooks.source.js that uses the shared hook utilities:
function transformHook(rw) {
// Props are accessed via rw.props - property IDs from properties.config.json
const { buttonText } = rw.props;
// Build CSS classes using the shared classnames utility
const classes = classnames()
.add(globalSpacing(rw))
.add(globalBgColor(rw))
.toString();
return {
classes,
buttonText
};
}
exports.transformHook = transformHook;
Note: The
buttonTextprop corresponds to the"id": "buttonText"defined inproperties.config.json. All property IDs become available onrw.props. Functions likeclassnames(),globalSpacing(), andglobalBgColor()come from the shared hooks library—no imports needed.
Add these scripts to your package.json:
{
"scripts": {
"build": "rw-build all",
"build:properties": "rw-build properties",
"build:hooks": "rw-build hooks",
"dev": "rw-build all --watch"
}
}
# One-time build
npm run build
# Or watch for changes during development
npm run dev
That's it! The build tool will generate properties.json and hooks.js files in each component folder.
# Build all properties and hooks
npx rw-build all
# Build properties only
npx rw-build properties
# Build hooks only
npx rw-build hooks
# Watch for changes
npx rw-build all --watch # Watch both properties and hooks
npx rw-build properties --watch # Watch properties only
npx rw-build hooks --watch # Watch hooks only
The packs directory can be configured via multiple methods (in priority order):
rw-build all --packs ./my-elements
RW_PACKS_DIR=./my-elements npm run build
{
"rw-elements-tools": {
"packsDir": "./my-elements"
}
}
// rw-elements-tools.config.js
export default {
packsDir: './my-elements'
}
If no configuration is provided, looks for ./packs in the project root.
The build system processes properties.config.json files located in element component directories and generates expanded properties.json files. This allows developers to:
globalControl referencesuse referencesproperties.config.json Controls (controls/)
│ │
▼ ▼
┌─────────────────────────────────────┐
│ build-properties.js │
│ • Expands globalControl references │
│ • Resolves 'use' property refs │
│ • Applies overrides & defaults │
│ • Injects Advanced group controls │
└─────────────────────────────────────┘
│
▼
properties.json
(consumed by RapidWeaver)
rw-elements-tools/
├── bin/
│ └── cli.js # CLI entry point (rw-build command)
├── build-properties.js # Properties build script
├── build-shared-hooks.js # Shared hooks build script
├── config.js # Configuration resolver
├── index.js # Package entry point
├── package.json # npm package config
├── README.md # This documentation
├── controls/ # Reusable UI control definitions
│ ├── index.js # Exports all controls
│ ├── alignment/ # Flexbox/Grid alignment controls
│ ├── Animations/ # Animation and scroll animation controls
│ ├── Background/ # Background color, image, gradient, video
│ ├── Borders/ # Border and outline controls
│ ├── core/ # Essential controls (ID, CSSClasses, etc.)
│ ├── Effects/ # Box shadow, opacity, filters, blur
│ ├── grid-flex/ # Grid and flexbox item controls
│ ├── interactive/ # Button, input, link, filter controls
│ ├── Layout/ # Position, overflow, visibility, z-index
│ ├── Overlay/ # Overlay color, gradient, image
│ ├── Sizing/ # Width, height, min/max sizing
│ ├── Spacing/ # Margin and padding controls
│ ├── Transforms/ # Rotate, scale, skew, translate
│ ├── Transitions/ # Transition timing and properties
│ └── typography/ # Text color, decoration, styles
├── properties/ # Reusable property value definitions
│ ├── index.js # Exports all properties
│ ├── Slider.js # Slider value ranges
│ ├── FontWeight.js # Font weight options
│ └── ... # Other property definitions
└── shared-hooks/ # Shared JavaScript hook functions
├── animations/ # Animation and reveal functions
├── background/ # Background processing functions
├── borders/ # Border and outline functions
├── core/ # Essential utilities (classnames, etc.)
├── effects/ # Visual effects (opacity, filters)
├── interactive/ # Link and filter functions
├── layout/ # Layout and positioning
├── navigation/ # Navigation component styles
├── sizing/ # Dimensions and aspect ratios
├── spacing/ # Margin and padding functions
├── transforms/ # CSS transform functions
├── transitions/ # CSS and Alpine transitions
└── typography/ # Text and font style functions
The properties build script is executed via:
cd src
npm run build
This runs node build.js which:
Finds all config files matching the glob pattern:
../**/*.elementsdevpack/components/com.**/**/properties.config.json
Processes each config file through the following pipeline:
globalControl referencesuse property referencesproperties.jsonFor each property in a config file:
┌─────────────────────────────────────────────────────────────┐
│ 1. Check for globalControl │
│ └─ If present: Load control from Controls registry │
│ └─ If absent: Pass through with 'use' resolution only │
├─────────────────────────────────────────────────────────────┤
│ 2. Deep clone the control (avoid mutations) │
├─────────────────────────────────────────────────────────────┤
│ 3. Apply property overrides │
│ └─ String values: Replace directly │
│ └─ Object values: Shallow merge │
├─────────────────────────────────────────────────────────────┤
│ 4. Apply default values │
│ └─ Primitive defaults: Set control.default │
│ └─ Object defaults: Merge into theme properties │
├─────────────────────────────────────────────────────────────┤
│ 5. Apply theme defaults │
│ └─ themeColor, themeFont, themeBorderRadius, etc. │
├─────────────────────────────────────────────────────────────┤
│ 6. Transform IDs using {{value}} template │
│ └─ "prefix{{value}}Suffix" → "prefixControlIdSuffix" │
├─────────────────────────────────────────────────────────────┤
│ 7. Process nested globalControls recursively │
├─────────────────────────────────────────────────────────────┤
│ 8. Resolve 'use' references to Properties │
└─────────────────────────────────────────────────────────────┘
The shared hooks build system combines reusable JavaScript utility functions with component-specific hook code, then applies dead code elimination to produce optimized output.
shared-hooks/**/*.js Component hooks.source.js
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ build-shared-hooks.js │
│ • Concatenates shared + component code │
│ • Applies esbuild dead code elimination │
│ • Keeps only code reachable from │
│ transformHook function │
└─────────────────────────────────────────┘
│
▼
hooks.js (optimized)
(consumed by RapidWeaver)
packs/ for hooks.source.js files.js files from shared-hooks/ and its subfoldershooks.js to each componentShared hooks are organized into category subfolders:
| Folder | Purpose | Example Functions |
|---|---|---|
core/ | Essential utilities | classnames, getHoverPrefix, globalHTMLTag |
layout/ | Layout and positioning | globalLayout, globalActAsGridOrFlexItem |
sizing/ | Dimensions and aspect ratios | globalSizing, aspectRatioClasses |
spacing/ | Margin and padding | globalSpacing, globalSpacingMargin |
background/ | Background styles | globalBackground, globalBgImageFetchPriority |
borders/ | Borders and outlines | globalBorders, globalOutline |
effects/ | Visual effects | globalEffects, globalFilters, globalOverlay |
typography/ | Text and font styles | globalTextFontsAndTextStyles, globalHeadingColor |
transforms/ | CSS transforms | globalTransforms |
transitions/ | CSS/Alpine transitions | globalTransitions, getAlpineTransitionAttributesMobile |
animations/ | Animations and reveals | globalAnimations, globalReveal |
navigation/ | Navigation styles | globalNavItems, globalMenuItem |
interactive/ | Links and filters | globalLink, globalFilter |
Each component that needs hooks creates a hooks.source.js file:
// packs/Core.elementsdevpack/components/com.realmacsoftware.button/hooks.source.js
function transformHook(rw) {
// Use any shared hook functions here
const classes = classnames(rw.props.customClasses)
.add(globalSpacing(rw))
.toString();
return {
classes
};
}
exports.transformHook = transformHook;
transformHook function is the only exported functiontransformHook is kepthooks.js files are marked "do not edit"# Build all hooks once
npm run build:hooks
# Watch for changes
npm run build:hooks:watch
# Using npm scripts
npm run build:hooks
Controls are reusable UI component definitions that map to RapidWeaver's property inspector UI elements.
A control is a JavaScript object (or array of objects) that defines:
// Simple control (single object)
const FlexDirection = {
title: "Direction",
id: "flexDirection",
select: {
default: "flex-col",
items: [
{ value: "flex-col", title: "Column" },
{ value: "flex-row", title: "Row" },
],
},
};
export default FlexDirection;
// Compound control (array of objects)
const Link = [
{
title: "Link",
heading: {}
},
{
title: "To",
id: "globalLink",
link: {}
}
];
export default Link;
Controls can use these UI element types:
| Type | Description | Example |
|---|---|---|
select | Dropdown menu | { select: { default: "value", items: [...] } } |
segmented | Segmented button control | { segmented: { default: "a", items: [...] } } |
switch | Boolean toggle | { switch: { default: false } } |
slider | Numeric slider | { slider: { default: 50, min: 0, max: 100 } } |
number | Numeric input | { number: { default: 0 } } |
text | Text input | { text: { default: "" } } |
textArea | Multi-line text | { textArea: { default: "" } } |
link | Link picker | { link: {} } |
resource | Resource/image picker | { resource: {} } |
heading | Section heading (no value) | { heading: {} } |
divider | Visual separator | { divider: {} } |
information | Info text | { information: {} } |
themeColor | Theme color picker | { themeColor: { default: {...} } } |
themeSpacing | Theme spacing picker | { themeSpacing: { mode: "single" } } |
themeBorderRadius | Border radius picker | { themeBorderRadius: {...} } |
themeBorderWidth | Border width picker | { themeBorderWidth: {...} } |
themeShadow | Shadow picker | { themeShadow: {...} } |
| Property | Type | Description |
|---|---|---|
title | string | Display label in the UI |
id | string | Property identifier (used in templates) |
format | string | CSS class format, e.g., "gap-x-{{value}}" |
visible | string | Visibility condition, e.g., "otherProp == 'value'" |
responsive | boolean | Whether control is responsive (default: true) |
globalControl | string | Reference to another control (nested) |
Controls can reference other controls for composition:
const Borders = [
{
globalControl: "ControlType",
id: "{{value}}Borders",
},
{
globalControl: "BorderStyle",
visible: "globalControlTypeBorders == 'static'",
},
{
globalControl: "BorderColor",
visible: "globalControlTypeBorders == 'static'",
},
];
Properties are reusable value definitions (like enums or option lists) that can be referenced using the use key.
// properties/FontWeight.js
const FontWeight = {
default: "normal",
items: [
{ value: "thin", title: "Thin" },
{ value: "light", title: "Light" },
{ value: "normal", title: "Normal" },
{ value: "medium", title: "Medium" },
{ value: "semibold", title: "Semibold" },
{ value: "bold", title: "Bold" },
],
};
export default FontWeight;
Reference a property using the use key:
const TextWeight = {
title: "Weight",
id: "textWeight",
select: {
use: "FontWeight" // Merges FontWeight's items and default
}
};
The build system will merge the referenced property, with local values taking precedence:
// Output
{
title: "Weight",
id: "textWeight",
select: {
default: "normal",
items: [
{ value: "thin", title: "Thin" },
// ... etc
]
}
}
{
"groups": [
{
"title": "Group Title",
"icon": "sf-symbol-name",
"properties": [
// Property definitions
]
}
]
}
Reference a control by name:
{
"globalControl": "Spacing"
}
Add properties alongside globalControl to override:
{
"globalControl": "BorderRadius",
"title": "Corner Radius",
"default": {
"base": {
"topLeft": "lg",
"topRight": "lg",
"bottomLeft": "none",
"bottomRight": "none"
}
}
}
Transform the control's ID using a template:
{
"globalControl": "Spacing",
"id": "card{{value}}"
}
If the Spacing control has id: "globalPadding", the output becomes id: "cardGlobalPadding".
Override theme-related properties:
{
"globalControl": "Background_Color",
"themeColor": {
"default": {
"name": "brand",
"brightness": 500
}
}
}
Supported theme properties:
themeColorthemeFontthemeBorderRadiusthemeBorderWidththemeSpacingthemeShadowthemeTextStyleProperties without globalControl are passed through with use resolution:
{
"title": "Custom Width",
"id": "customWidth",
"slider": {
"use": "Slider",
"default": 100,
"min": 0,
"max": 500
}
}
Create a new file in the appropriate controls/ subdirectory:
// controls/Effects/NewEffect.js
const NewEffect = {
title: "Effect Intensity",
id: "effectIntensity",
format: "effect-[{{value}}%]",
slider: {
default: 50,
min: 0,
max: 100,
round: true,
units: "%"
}
};
export default NewEffect;
Add the export to controls/index.js:
// In the appropriate section
export { default as NewEffect } from "./Effects/NewEffect.js";
Reference in any properties.config.json:
{
"globalControl": "NewEffect"
}
cd src
npm run build
For controls with multiple UI elements:
// controls/interactive/CustomButton.js
const CustomButton = [
{
title: "Button Settings",
heading: {}
},
{
title: "Style",
id: "buttonStyle",
segmented: {
default: "solid",
items: [
{ value: "solid", title: "Solid" },
{ value: "outline", title: "Outline" },
{ value: "ghost", title: "Ghost" }
]
}
},
{
title: "Size",
id: "buttonSize",
select: {
use: "ButtonSize"
}
}
];
export default CustomButton;
// controls/Layout/CustomLayout.js
const CustomLayout = [
{
globalControl: "ControlType",
id: "{{value}}CustomLayout"
},
{
visible: "globalControlTypeCustomLayout != 'none'",
divider: {}
},
{
visible: "globalControlTypeCustomLayout != 'none'",
globalControl: "Position"
},
{
visible: "globalControlTypeCustomLayout != 'none'",
globalControl: "ZIndex"
}
];
export default CustomLayout;
// properties/CustomSizes.js
const CustomSizes = {
default: "md",
items: [
{ value: "xs", title: "Extra Small" },
{ value: "sm", title: "Small" },
{ value: "md", title: "Medium" },
{ value: "lg", title: "Large" },
{ value: "xl", title: "Extra Large" },
]
};
export default CustomSizes;
// properties/index.js
export { default as CustomSizes } from "./CustomSizes.js";
const SizeSelector = {
title: "Size",
id: "elementSize",
select: {
use: "CustomSizes"
}
};
Or use directly in config files:
{
"title": "Size",
"id": "mySize",
"select": {
"use": "CustomSizes"
}
}
Create a new file in the appropriate shared-hooks/ subfolder:
// shared-hooks/effects/customEffect.js
/**
* Generate custom effect classes based on element properties
* @param {Object} rw - The RapidWeaver element object
* @returns {string} CSS class string
*/
function customEffect(rw) {
const { customEnabled, customIntensity } = rw.props;
if (!customEnabled) return '';
return classnames([
'custom-effect',
customIntensity && `intensity-${customIntensity}`
]).toString();
}
Reference the function in any hooks.source.js:
// packs/MyPack.elementsdevpack/components/com.example.mycomponent/hooks.source.js
function transformHook(rw) {
// customEffect is available from shared hooks (no import needed)
const effectClasses = customEffect(rw);
return {
effectClasses
};
}
exports.transformHook = transformHook;
npm run build:hooks
global: For element property processing functions (e.g., globalSpacing)| Category | Folder | Example |
|---|---|---|
| Core utilities | core/ | classnames.js, getHoverPrefix.js |
| Layout functions | layout/ | globalLayout.js |
| Visual effects | effects/ | globalEffects.js |
| Typography | typography/ | globalHeadingColor.js |
The build system automatically removes unused code. If you add a function to shared hooks but no component uses it, it won't appear in any output hooks.js file. This keeps the output lean.
// shared-hooks/layout/globalLayout.js
/**
* Generate layout-related CSS classes
*/
const globalLayout = (app, args = {}) => {
const {
globalLayoutPosition: position,
globalLayoutZIndex: zIndex,
globalLayoutOverflow: overflow,
} = app.props;
return classnames([
position,
zIndex,
overflow,
]).toString();
};
Show/hide controls based on other property values:
{
"title": "Custom Value",
"id": "customValue",
"visible": "sizeType == 'custom'",
"number": {
"default": 100
}
}
Complex conditions:
{
"visible": "enableFeature == true && mode == 'advanced'"
}
Generate CSS class names from values:
{
"id": "gapX",
"format": "gap-x-{{value}}",
"themeSpacing": {
"default": { "base": { "value": "4" } }
}
}
Output when value is "8": gap-x-8
By default, controls are responsive. Disable with:
{
"id": "staticValue",
"responsive": false,
"text": { "default": "" }
}
The build system automatically:
CSSClasses and ID controls at the start of the Advanced groupTo add controls to the Advanced group:
{
"groups": [
{
"title": "Advanced",
"properties": [
{ "divider": {} },
{ "globalControl": "HTMLTag" }
]
}
]
}
# Build everything (properties + hooks)
rw-build all
# Build properties only
rw-build properties
# Build hooks only
rw-build hooks
# Watch for changes
rw-build all --watch # Watch both properties and hooks
rw-build properties --watch # Watch properties only
rw-build hooks --watch # Watch hooks only
# Build with custom packs directory
rw-build all --packs ./my-elements
# Show help
rw-build --help
Add these to your package.json:
{
"scripts": {
"build": "rw-build all",
"build:properties": "rw-build properties",
"build:hooks": "rw-build hooks",
"dev": "rw-build all --watch"
}
}
Then run:
npm run build
npm run dev
The --watch flag monitors for changes and automatically rebuilds:
| Command | Watches |
|---|---|
rw-build all --watch | Both properties and hooks (runs watchers concurrently) |
rw-build properties --watch | properties.config.json files in packs/ |
rw-build hooks --watch | hooks.source.js files in packs/ and shared-hooks/*.js |
"Global control 'X' not found"
controls/index.js"Property 'X' not found in Properties"
properties/index.jsuse key spellingBuild produces unexpected output
globalControl references--trace-warnings flag for more details{
"groups": [
{
"title": "Content",
"icon": "text.alignleft",
"properties": [
{
"title": "Heading",
"id": "headingText",
"text": {
"default": "Welcome"
}
},
{
"globalControl": "HeadingColor"
}
]
},
{
"title": "Layout",
"icon": "square.split.bottomrightquarter",
"properties": [
{
"globalControl": "Layout"
}
]
},
{
"title": "Spacing",
"icon": "squareshape.squareshape.dotted",
"properties": [
{
"globalControl": "Spacing"
}
]
},
{
"title": "Background",
"icon": "paintbrush.fill",
"properties": [
{
"globalControl": "BackgroundTransparent"
}
]
},
{
"title": "Borders",
"icon": "square.dashed",
"properties": [
{
"globalControl": "Borders"
}
]
},
{
"title": "Advanced",
"properties": [
{
"divider": {}
},
{
"globalControl": "HTMLTag"
}
]
}
]
}
You can also use rw-elements-tools programmatically in your own build scripts:
import {
buildProperties,
buildHooks,
watchProperties,
watchHooks,
resolveConfig,
Controls,
Properties
} from 'rw-elements-tools';
// Resolve configuration from all sources
const config = await resolveConfig({
packs: './my-elements' // Optional CLI override
});
// Build properties
await buildProperties(config);
// Build hooks
await buildHooks(config);
// Watch for changes
await watchProperties(config); // Watch properties only
await watchHooks(config); // Watch hooks only
// Or watch both concurrently
await Promise.all([
watchProperties(config),
watchHooks(config)
]);
import { Controls, Properties } from 'rw-elements-tools';
// Use a control definition
console.log(Controls.Spacing);
console.log(Controls.BorderRadius);
// Use a property definition
console.log(Properties.FontWeight);
console.log(Properties.Slider);
import { resolveConfig, buildProperties, buildHooks } from 'rw-elements-tools';
async function customBuild() {
const config = await resolveConfig();
console.log('Building for:', config.packsDir);
// Build properties first
await buildProperties(config);
// Then build hooks
await buildHooks(config);
console.log('Build complete!');
}
customBuild();
To publish the package to npm:
cd src
npm login
npm publish
For scoped packages:
npm publish --access public
When adding new controls or properties:
index.jsnpm run build and verify outputLast updated: January 2026
FAQs
Build tools for RapidWeaver Elements packs - generates properties.json and hooks.js files
We found that rw-elements-tools demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.