runtime

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 3, 2025 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package runtime provides JavaScript execution capabilities for LESS plugins.

This package implements a bridge between Go and Node.js, allowing LESS plugins written in JavaScript to be executed while maintaining performance through shared memory buffer transfer.

Architecture Overview

The runtime uses a hybrid approach combining Node.js for JavaScript execution with shared memory for efficient data transfer:

  1. Go spawns a persistent Node.js process running plugin-host.js
  2. Commands are sent via stdin/stdout (JSON protocol)
  3. Large data (ASTs) is transferred via shared memory buffers
  4. Node.js executes plugin JavaScript and writes results back

This approach provides:

  • Perfect JavaScript compatibility (real Node.js)
  • Fast V8 execution (10x faster than embedded runtimes like goja)
  • Zero-copy data transfer (no JSON serialization for ASTs)
  • Simple plugin loading (native require())

Usage

rt, err := runtime.NewNodeJSRuntime()
if err != nil {
    log.Fatal(err)
}
defer rt.Stop()

// Send a command
response, err := rt.SendCommand(runtime.Command{
    Cmd: "ping",
})
if err != nil {
    log.Fatal(err)
}

IPC Protocol

Commands are JSON objects sent via stdin:

{"id": 1, "cmd": "ping"}
{"id": 2, "cmd": "loadPlugin", "path": "./plugin.js"}
{"id": 3, "cmd": "callFunction", "functionID": "myFunc", "args": [...]}

Responses are JSON objects received via stdout:

{"id": 1, "success": true, "result": "pong"}
{"id": 2, "success": true, "result": {"functions": ["myFunc"]}}
{"id": 3, "success": false, "error": "function not found"}

Index

Constants

View Source
const (
	FlagParens      uint16 = 1 << 0
	FlagParensInOp  uint16 = 1 << 1
	FlagVisible     uint16 = 1 << 2
	FlagInvisible   uint16 = 1 << 3
	FlagVisibleSet  uint16 = 1 << 4
	FlagHasFileInfo uint16 = 1 << 5
	FlagHasIndex    uint16 = 1 << 6
)

FlatNode flags

View Source
const (
	FlatASTMagic   uint32 = 0x4C455353 // "LESS"
	FlatASTVersion uint32 = 1
	FlatNodeSize   int    = 24 // bytes per FlatNode
)

Binary format constants

View Source
const (
	PrefetchMagic   uint32 = 0x50524546 // "PREF"
	PrefetchVersion uint32 = 1

	// Variable types
	VarTypeNull       byte = 0
	VarTypeDimension  byte = 1
	VarTypeColor      byte = 2
	VarTypeQuoted     byte = 3
	VarTypeKeyword    byte = 4
	VarTypeExpression byte = 5
	VarTypeAnonymous  byte = 6
	VarTypeVariable   byte = 7 // Variable reference (name only, needs lookup)
)
View Source
const (
	// Section sizes
	ControlBlockSize     = 4 * 1024        // 4KB
	VariablesSectionSize = 1 * 1024 * 1024 // 1MB
	ArgsSectionSize      = 1 * 1024 * 1024 // 1MB
	ResultsSectionSize   = 1 * 1024 * 1024 // 1MB
	ErrorBufferSize      = 64 * 1024       // 64KB
	TotalSHMSize         = ControlBlockSize + VariablesSectionSize + ArgsSectionSize + ResultsSectionSize + ErrorBufferSize

	// Section offsets
	ControlBlockOffset     = 0
	VariablesSectionOffset = ControlBlockSize
	ArgsSectionOffset      = VariablesSectionOffset + VariablesSectionSize
	ResultsSectionOffset   = ArgsSectionOffset + ArgsSectionSize
	ErrorBufferOffset      = ResultsSectionOffset + ResultsSectionSize

	// Control block field offsets (relative to control block start)
	OffsetRequestReady  = 0x000
	OffsetResponseReady = 0x004
	OffsetFunctionID    = 0x008
	OffsetArgCount      = 0x00C
	OffsetArgOffsets    = 0x010 // Array of 16 uint32s
	OffsetResultOffset  = 0x050
	OffsetResultSize    = 0x054
	OffsetErrorFlag     = 0x058
	OffsetErrorOffset   = 0x05C
	OffsetErrorSize     = 0x060
	OffsetShutdown      = 0x064
	OffsetJSReady       = 0x068

	MaxArgs = 16

	// Argument types for binary serialization
	ArgTypeNull       = 0
	ArgTypeDimension  = 1
	ArgTypeColor      = 2
	ArgTypeQuoted     = 3
	ArgTypeKeyword    = 4
	ArgTypeExpression = 5
	ArgTypeAnonymous  = 6
	ArgTypeVariable   = 7
	ArgTypeNumber     = 8
	ArgTypeBoolean    = 9
	ArgTypeCall       = 10
)

Variables

View Source
var TypeNameToID = func() map[string]NodeTypeID {
	m := make(map[string]NodeTypeID)
	for id, name := range TypeNames {
		m[name] = id
	}
	return m
}()

TypeNameToID maps string type names to their IDs.

View Source
var TypeNames = map[NodeTypeID]string{
	TypeUnknown:           "Unknown",
	TypeAnonymous:         "Anonymous",
	TypeAssignment:        "Assignment",
	TypeAtRule:            "AtRule",
	TypeAttribute:         "Attribute",
	TypeCall:              "Call",
	TypeColor:             "Color",
	TypeCombinator:        "Combinator",
	TypeComment:           "Comment",
	TypeCondition:         "Condition",
	TypeContainer:         "Container",
	TypeDeclaration:       "Declaration",
	TypeDetachedRuleset:   "DetachedRuleset",
	TypeDimension:         "Dimension",
	TypeElement:           "Element",
	TypeExpression:        "Expression",
	TypeExtend:            "Extend",
	TypeImport:            "Import",
	TypeJavaScript:        "JavaScript",
	TypeKeyword:           "Keyword",
	TypeMedia:             "Media",
	TypeMixinCall:         "MixinCall",
	TypeMixinDefinition:   "MixinDefinition",
	TypeNamespaceValue:    "NamespaceValue",
	TypeNegative:          "Negative",
	TypeOperation:         "Operation",
	TypeParen:             "Paren",
	TypeProperty:          "Property",
	TypeQueryInParens:     "QueryInParens",
	TypeQuoted:            "Quoted",
	TypeRuleset:           "Ruleset",
	TypeSelector:          "Selector",
	TypeSelectorList:      "SelectorList",
	TypeUnicodeDescriptor: "UnicodeDescriptor",
	TypeUnit:              "Unit",
	TypeURL:               "URL",
	TypeValue:             "Value",
	TypeVariable:          "Variable",
	TypeVariableCall:      "VariableCall",
	TypeNode:              "Node",
}

TypeNames maps type IDs to their string names (for JavaScript compatibility).

Functions

func ClearJSFunctionDefinitionCache

func ClearJSFunctionDefinitionCache()

ClearJSFunctionDefinitionCache clears all cached JSFunctionDefinition objects. This should be called when shutting down or when you need to force fresh objects.

func ClearJSFunctionDefinitionCacheForRuntime

func ClearJSFunctionDefinitionCacheForRuntime(runtime *NodeJSRuntime)

ClearJSFunctionDefinitionCacheForRuntime clears cached JSFunctionDefinition objects for a specific runtime instance. This should be called when a runtime is stopped.

func GetContextVersion

func GetContextVersion() uint64

GetContextVersion returns the current context version.

func GetJSFunctionDefinitionCacheStats

func GetJSFunctionDefinitionCacheStats() int

GetJSFunctionDefinitionCacheStats returns the number of cached JSFunctionDefinition objects.

func IncrementContextVersion

func IncrementContextVersion()

IncrementContextVersion should be called when frames are pushed/popped to invalidate the context cache.

func WritePrefetchedVariables

func WritePrefetchedVariables(variables map[string]any) []byte

WritePrefetchedVariables writes a map of prefetched variables to binary format. Returns the binary buffer ready to be written to shared memory.

Types

type ASTFlattener

type ASTFlattener struct {
	// contains filtered or unexported fields
}

ASTFlattener converts Go AST nodes to FlatAST format.

func NewASTFlattener

func NewASTFlattener() *ASTFlattener

NewASTFlattener creates a new AST flattener.

func (*ASTFlattener) Flatten

func (af *ASTFlattener) Flatten(root any) (*FlatAST, error)

Flatten flattens an entire AST starting from the root.

func (*ASTFlattener) FlattenNode

func (af *ASTFlattener) FlattenNode(node any, parentIndex uint32) (uint32, error)

FlattenNode flattens a single node and its children recursively. Returns the index of the flattened node.

type ASTUnflattener

type ASTUnflattener struct {
	// contains filtered or unexported fields
}

ASTUnflattener reconstructs AST nodes from FlatAST format.

func NewASTUnflattener

func NewASTUnflattener(flat *FlatAST) *ASTUnflattener

NewASTUnflattener creates a new AST unflattener.

func (*ASTUnflattener) Unflatten

func (au *ASTUnflattener) Unflatten() (*GenericNode, error)

Unflatten reconstructs the AST from the flat representation. Returns the root GenericNode.

type BaseNode

type BaseNode interface {
	GetNode() *Node
}

BaseNode interface for nodes that embed *Node

type BatchCall

type BatchCall struct {
	// Key is a unique identifier for this call (e.g., "funcName:arg1|arg2")
	// Used to correlate requests with responses in the batch result map.
	Key string `json:"key"`
	// Name is the function name to call
	Name string `json:"name"`
	// Args are the serialized arguments for the function
	Args []any `json:"args"`
	// Context is the optional evaluation context for variable lookups
	Context map[string]any `json:"context,omitempty"`
}

BatchCall represents a single function call in a batch. This is used by BatchCallFunctions to reduce IPC overhead.

type BatchCallResult

type BatchCallResult struct {
	// Success indicates if the call succeeded
	Success bool `json:"success"`
	// Result is the function return value (if successful)
	Result any `json:"result,omitempty"`
	// Error is the error message (if failed)
	Error string `json:"error,omitempty"`
}

BatchCallResult represents the result of a single call in a batch.

type BinaryVariableWriter

type BinaryVariableWriter struct {
	// contains filtered or unexported fields
}

BinaryVariableWriter writes variables in binary format to a buffer.

func NewBinaryVariableWriter

func NewBinaryVariableWriter() *BinaryVariableWriter

NewBinaryVariableWriter creates a new binary variable writer.

func (*BinaryVariableWriter) Bytes

func (w *BinaryVariableWriter) Bytes() []byte

Bytes returns the written buffer.

func (*BinaryVariableWriter) Reset

func (w *BinaryVariableWriter) Reset()

Reset clears the buffer for reuse.

func (*BinaryVariableWriter) WriteHeader

func (w *BinaryVariableWriter) WriteHeader(varCount uint32)

WriteHeader writes the prefetch buffer header.

func (*BinaryVariableWriter) WriteVariable

func (w *BinaryVariableWriter) WriteVariable(name string, decl any) (int, error)

WriteVariable writes a single variable to the buffer. Returns the number of bytes written.

type CallbackHandler

type CallbackHandler func(data any) (any, error)

CallbackHandler is a function that handles callbacks from Node.js.

type CallbackRequest

type CallbackRequest struct {
	ID       int64  `json:"id"`
	Callback string `json:"callback"`
	Data     any    `json:"data,omitempty"`
}

CallbackRequest represents a callback request from Node.js to Go. This is used for on-demand variable lookup during function execution.

type Command

type Command struct {
	ID   int64  `json:"id"`
	Cmd  string `json:"cmd"`
	Data any    `json:"data,omitempty"`
}

Command represents a command sent to the Node.js process.

type EvalContextProvider

type EvalContextProvider interface {
	GetFramesAny() []any
	GetImportantScopeAny() []map[string]any
}

EvalContextProvider is an interface for objects that can provide evaluation context for JavaScript plugin functions that need to access variables.

type FileManagerCollection

type FileManagerCollection struct {
	// contains filtered or unexported fields
}

FileManagerCollection manages JavaScript file managers for a plugin loader.

func NewFileManagerCollection

func NewFileManagerCollection(runtime *NodeJSRuntime) *FileManagerCollection

NewFileManagerCollection creates a new file manager collection.

func (*FileManagerCollection) FileManagerCount

func (fmc *FileManagerCollection) FileManagerCount() int

FileManagerCount returns the number of registered file managers.

func (*FileManagerCollection) FindSupportingManager

func (fmc *FileManagerCollection) FindSupportingManager(filename, currentDirectory string, options map[string]any) *JSFileManager

FindSupportingManager finds the first file manager that supports the given file. Returns nil if no file manager supports the file.

func (*FileManagerCollection) GetFileManagers

func (fmc *FileManagerCollection) GetFileManagers() []*JSFileManager

GetFileManagers returns all registered file managers.

func (*FileManagerCollection) LoadFile

func (fmc *FileManagerCollection) LoadFile(filename, currentDirectory string, options map[string]any) (*LoadedFile, error)

LoadFile tries to load a file using the registered file managers. It tries each file manager in order and returns the first successful result. Returns an error if no file manager can load the file.

func (*FileManagerCollection) RefreshFileManagers

func (fmc *FileManagerCollection) RefreshFileManagers() error

RefreshFileManagers fetches the current list of registered file managers from Node.js.

type FileManagerInfo

type FileManagerInfo struct {
	Index int `json:"index"`
}

FileManagerInfo contains metadata about a registered JavaScript file manager.

type FlatAST

type FlatAST struct {
	// Header information
	Version   uint32 // Format version
	NodeCount uint32 // Number of nodes
	RootIndex uint32 // Index of root node

	// Tables
	Nodes       []FlatNode // Array of flat nodes
	TypeTable   []string   // Node type names (for validation)
	StringTable []string   // String values
	PropBuffer  []byte     // Node-specific properties (JSON encoded)
	// contains filtered or unexported fields
}

FlatAST represents a complete flattened AST structure.

func FlattenAST

func FlattenAST(root any) (*FlatAST, error)

FlattenAST is a convenience function to flatten an AST.

func FromBytes

func FromBytes(data []byte) (*FlatAST, error)

FromBytes deserializes a byte buffer to a FlatAST.

func NewFlatAST

func NewFlatAST() *FlatAST

NewFlatAST creates a new empty FlatAST.

func (*FlatAST) AddNode

func (f *FlatAST) AddNode(node FlatNode) uint32

AddNode adds a node to the AST and returns its index.

func (*FlatAST) AddProperties

func (f *FlatAST) AddProperties(props map[string]any) (uint32, uint32)

AddProperties adds properties to the buffer and returns offset and length.

func (*FlatAST) AddString

func (f *FlatAST) AddString(s string) uint32

AddString adds a string to the string table and returns its index. Duplicate strings are deduplicated.

func (*FlatAST) GetProperties

func (f *FlatAST) GetProperties(offset, length uint32) map[string]any

GetProperties retrieves properties from the buffer.

func (*FlatAST) GetString

func (f *FlatAST) GetString(idx uint32) string

GetString retrieves a string from the string table by index.

func (*FlatAST) ToBytes

func (f *FlatAST) ToBytes() ([]byte, error)

ToBytes serializes the FlatAST to a byte buffer.

type FlatNode

type FlatNode struct {
	TypeID      NodeTypeID // Index into type table (2 bytes)
	Flags       uint16     // Node flags (2 bytes)
	ChildIndex  uint32     // Index of first child in Nodes array (0 if none)
	NextIndex   uint32     // Index of next sibling (0 if none)
	ParentIndex uint32     // Index of parent node (0 if root)
	PropsOffset uint32     // Offset into properties buffer
	PropsLength uint32     // Length of properties in buffer
}

FlatNode represents a node in the flattened AST buffer. Each node is 24 bytes in the binary representation.

type GenericNode

type GenericNode struct {
	Type       string
	Properties map[string]any
	Children   []*GenericNode
	Parent     *GenericNode
	Index      int
	Parens     bool
	ParensInOp bool
	Visible    *bool
	FileInfo   map[string]any
}

GenericNode represents a reconstructed AST node from flat format. This is used when we don't have the original Go type constructors.

func UnflattenAST

func UnflattenAST(flat *FlatAST) (*GenericNode, error)

UnflattenAST is a convenience function to unflatten an AST.

func (*GenericNode) GetBool

func (g *GenericNode) GetBool(propName string) (bool, bool)

GetBool retrieves a boolean property.

func (*GenericNode) GetFloat64

func (g *GenericNode) GetFloat64(propName string) (float64, bool)

GetFloat64 retrieves a float64 property.

func (*GenericNode) GetType

func (g *GenericNode) GetType() string

GetType returns the node type name.

func (*GenericNode) ResolveString

func (g *GenericNode) ResolveString(flat *FlatAST, propName string) string

ResolveString resolves a string index to its value.

func (*GenericNode) ToJSON

func (g *GenericNode) ToJSON() map[string]any

ToJSON converts the GenericNode tree to a JSON-serializable map.

type JSFileManager

type JSFileManager struct {
	Index int
	// contains filtered or unexported fields
}

JSFileManager wraps a JavaScript file manager registered by a plugin. File managers provide custom import resolution logic for LESS files.

func NewJSFileManager

func NewJSFileManager(runtime *NodeJSRuntime, index int) *JSFileManager

NewJSFileManager creates a new JSFileManager wrapper.

func (*JSFileManager) LoadFile

func (fm *JSFileManager) LoadFile(filename, currentDirectory string, options map[string]any) (*LoadedFile, error)

LoadFile loads a file using this file manager. It sends a request to Node.js to call the file manager's loadFile() method.

func (*JSFileManager) Supports

func (fm *JSFileManager) Supports(filename, currentDirectory string, options map[string]any) (bool, error)

Supports checks if this file manager can handle the given file. It sends a request to Node.js to call the file manager's supports() method.

type JSFunctionDefinition

type JSFunctionDefinition struct {
	// contains filtered or unexported fields
}

JSFunctionDefinition implements the FunctionDefinition interface for JavaScript functions. It calls JavaScript functions registered by plugins via the Node.js runtime.

The function supports two IPC modes for communicating with Node.js:

  • Shared Memory: Zero-copy transfer using memory-mapped files (default)
  • JSON: Traditional JSON serialization over stdio

See the package-level documentation for details on configuring the IPC mode.

OPTIMIZATION: JSFunctionDefinition objects are cached and reused via GetOrCreateJSFunctionDefinition. Result caching happens at the runtime level via GetCachedResult/SetCachedResult, which provides a 95%+ cache hit rate for Bootstrap-style compilations.

CONTEXT-FREE FUNCTIONS: Functions marked as contextFree=true are pure functions that don't need access to LESS variables or evaluation context. These functions:

  • Skip context serialization entirely (no frame/variable data sent)
  • Use a simpler, faster IPC path
  • Can be more aggressively cached
  • Examples: math operations, color transformations, string utilities

func GetOrCreateJSFunctionDefinition

func GetOrCreateJSFunctionDefinition(name string, runtime *NodeJSRuntime, opts ...JSFunctionOption) *JSFunctionDefinition

GetOrCreateJSFunctionDefinition retrieves a cached JSFunctionDefinition or creates a new one. This is the primary function to use when you need a JSFunctionDefinition - it ensures that the same object is reused for the same (runtime, name) combination.

Thread-safe: Uses read lock for cache hits, write lock only for cache misses.

OPTIMIZATION: Uses a two-level map (runtime -> name -> definition) to avoid creating string keys on every lookup. The function name string is reused directly.

func NewJSFunctionDefinition

func NewJSFunctionDefinition(name string, runtime *NodeJSRuntime, opts ...JSFunctionOption) *JSFunctionDefinition

NewJSFunctionDefinition creates a new JSFunctionDefinition for calling JavaScript functions registered by plugins.

The default IPC mode is determined by:

  1. Any options passed (WithJSONMode, WithSharedMemoryMode, WithIPCMode)
  2. The LESS_JS_IPC_MODE environment variable
  3. Shared memory mode (if nothing else is specified)

Example usage:

// Use default mode (shared memory, or env var override)
fn := NewJSFunctionDefinition("myFunc", runtime)

// Explicitly use JSON mode
fn := NewJSFunctionDefinition("myFunc", runtime, WithJSONMode())

// Explicitly use shared memory mode
fn := NewJSFunctionDefinition("myFunc", runtime, WithSharedMemoryMode())

OPTIMIZATION: Prefer using GetOrCreateJSFunctionDefinition instead of this function directly to benefit from object reuse caching.

func (*JSFunctionDefinition) CacheStats

func (jf *JSFunctionDefinition) CacheStats() int

CacheStats returns the number of cached entries for this function. Note: Returns 0 since individual function cache stats are not tracked at the runtime level. Use GetJSFunctionDefinitionCacheStats() for the global object cache stats.

func (*JSFunctionDefinition) Call

func (jf *JSFunctionDefinition) Call(args ...any) (any, error)

Call calls the JavaScript function with the given arguments.

The IPC mode (shared memory or JSON) is determined by the function's configuration. See NewJSFunctionDefinition for details on mode selection.

Returns the result node or error.

func (*JSFunctionDefinition) CallCtx

func (jf *JSFunctionDefinition) CallCtx(ctx any, args ...any) (any, error)

CallCtx calls the JavaScript function with context. For JS functions, we ignore the context and just call Call.

func (*JSFunctionDefinition) CallWithContext

func (jf *JSFunctionDefinition) CallWithContext(evalContext EvalContextProvider, args ...any) (any, error)

CallWithContext calls the JavaScript function with evaluation context. This is used by plugin functions that need to access Less variables.

OPTIMIZATION: Uses multiple strategies for optimal performance: 1. For context-free functions, skip context serialization entirely (fastest path) 2. Check result cache first (same args = same result) 3. If SHM protocol is available, use binary protocol 4. Otherwise, use pre-fetch + on-demand lookup

func (*JSFunctionDefinition) ClearCache

func (jf *JSFunctionDefinition) ClearCache()

ClearCache clears the result cache for this function. This clears entries from the runtime-level cache that are keyed with this function's name. Call this between compilations if you want to ensure fresh results.

func (*JSFunctionDefinition) IPCMode

func (jf *JSFunctionDefinition) IPCMode() JSIPCMode

IPCMode returns the current IPC mode for this function.

func (*JSFunctionDefinition) IsContextFree

func (jf *JSFunctionDefinition) IsContextFree() bool

IsContextFree returns true if this function is marked as context-free (pure). Context-free functions don't need access to LESS variables or evaluation context.

func (*JSFunctionDefinition) Name

func (jf *JSFunctionDefinition) Name() string

Name returns the function name.

func (*JSFunctionDefinition) NeedsEvalArgs

func (jf *JSFunctionDefinition) NeedsEvalArgs() bool

NeedsEvalArgs returns true - JS functions always expect evaluated arguments.

func (*JSFunctionDefinition) SetContextFree

func (jf *JSFunctionDefinition) SetContextFree(contextFree bool)

SetContextFree sets whether this function is context-free. This is useful for dynamically updating a function's context requirements.

type JSFunctionOption

type JSFunctionOption func(*JSFunctionDefinition)

JSFunctionOption configures a JSFunctionDefinition.

func WithCaching

func WithCaching() JSFunctionOption

WithCaching enables result caching for deterministic functions. When enabled, function calls with the same arguments will return cached results instead of making IPC calls to Node.js.

This is highly effective for Bootstrap-style plugins where functions like map-get, color-yiq, breakpoint-next are called many times with the same args.

Use this only for functions that are deterministic (same args always = same result) during a single compilation.

Note: Caching is ALWAYS ENABLED at the runtime level via GetCachedResult/SetCachedResult. This option is kept for API compatibility but has no effect - caching cannot be disabled.

func WithContext

func WithContext() JSFunctionOption

WithContext marks the function as requiring context (not pure). This is the default, but can be used to explicitly override a cached function's setting.

func WithContextFree

func WithContextFree() JSFunctionOption

WithContextFree marks the function as context-free (pure). Context-free functions don't need access to LESS variables or evaluation context. This enables significant performance optimizations:

  • Skip context serialization (no frame/variable data sent to JS)
  • Use a simpler, faster IPC path
  • More aggressive caching (pure functions are deterministic)

Use this for functions that:

  • Only operate on their input arguments
  • Don't access LESS variables (via this.context)
  • Don't have side effects
  • Always return the same output for the same input

Examples of context-free functions:

  • Math operations (add, multiply, sqrt, etc.)
  • Color transformations (lighten, darken, saturate)
  • String utilities (replace, split, join)
  • Type checking (isnumber, iscolor, isstring)

Examples of functions that NEED context:

  • Functions that access variables (map-get with variable maps)
  • Functions that use `this.context` for variable lookup
  • Functions with side effects or state

func WithIPCMode

func WithIPCMode(mode JSIPCMode) JSFunctionOption

WithIPCMode configures the function to use the specified IPC mode. This allows programmatic control over the IPC mode.

func WithJSONMode

func WithJSONMode() JSFunctionOption

WithJSONMode configures the function to use JSON serialization for IPC. This mode serializes arguments and results as JSON, which is simpler but has serialization overhead compared to shared memory mode.

Use this when:

  • Debugging IPC issues (JSON is easier to inspect)
  • Running in environments without shared memory support
  • Working with simple function calls where overhead doesn't matter

func WithSharedMemoryMode

func WithSharedMemoryMode() JSFunctionOption

WithSharedMemoryMode configures the function to use shared memory for IPC. This is the default mode but can be explicitly set to override environment variable configuration.

This mode serializes arguments to FlatAST binary format and writes them to a memory-mapped file that Node.js can read directly.

Use this when:

  • Working with complex AST trees
  • Performance is critical
  • Making many function calls

func WithoutCaching

func WithoutCaching() JSFunctionOption

WithoutCaching disables result caching. Use this for functions that are non-deterministic (may return different results for the same arguments due to side effects or external state).

Note: This option is kept for API compatibility but has no effect. Caching happens at the runtime level and provides 95%+ hit rate.

type JSIPCMode

type JSIPCMode int

JSIPCMode represents the IPC mode for JS function calls.

const (
	// JSIPCModeSharedMemory uses shared memory for zero-copy data transfer.
	// Arguments are serialized to FlatAST format and written to a memory-mapped file.
	// This is the default mode.
	JSIPCModeSharedMemory JSIPCMode = iota

	// JSIPCModeJSON uses JSON serialization over stdio pipes.
	// Simpler but involves serialization/deserialization overhead.
	JSIPCModeJSON
)

func ParseIPCMode

func ParseIPCMode(mode string) JSIPCMode

ParseIPCMode parses an IPC mode string into a JSIPCMode value. Recognized values: "json", "JSON", "sharedmem", "shm", "shared", "SHM", "SHARED" Returns JSIPCModeJSON for unrecognized values (safe default).

func (JSIPCMode) String

func (m JSIPCMode) String() string

String returns a human-readable name for the IPC mode.

type JSPluginLoader

type JSPluginLoader struct {
	// contains filtered or unexported fields
}

JSPluginLoader loads JavaScript plugins via the Node.js runtime. It implements the PluginLoader interface from the main less_go package.

func NewJSPluginLoader

func NewJSPluginLoader(runtime *NodeJSRuntime) *JSPluginLoader

NewJSPluginLoader creates a new JavaScript plugin loader.

func (*JSPluginLoader) CallFunction

func (pl *JSPluginLoader) CallFunction(name string, args []any) (any, error)

CallFunction calls a JavaScript function registered by a plugin.

func (*JSPluginLoader) ClearCache

func (pl *JSPluginLoader) ClearCache()

ClearCache clears the plugin cache.

func (*JSPluginLoader) EvalPlugin

func (pl *JSPluginLoader) EvalPlugin(contents string, newEnv any, importManager any, pluginArgs map[string]any, newFileInfo any) any

EvalPlugin evaluates plugin contents directly (for inline plugin code).

func (*JSPluginLoader) GetLoadedPlugins

func (pl *JSPluginLoader) GetLoadedPlugins() map[string]*Plugin

GetLoadedPlugins returns a copy of all loaded plugins.

func (*JSPluginLoader) GetPlugin

func (pl *JSPluginLoader) GetPlugin(cacheKey string) (*Plugin, bool)

GetPlugin retrieves a loaded plugin by its cache key.

func (*JSPluginLoader) GetRegisteredFunctions

func (pl *JSPluginLoader) GetRegisteredFunctions() ([]string, error)

GetRegisteredFunctions returns the names of all functions registered by plugins.

func (*JSPluginLoader) GetVisitors

func (pl *JSPluginLoader) GetVisitors() ([]map[string]any, error)

GetVisitors returns information about registered visitors.

func (*JSPluginLoader) LoadPlugin

func (pl *JSPluginLoader) LoadPlugin(path, currentDirectory string, context map[string]any, environment any, fileManager any) any

LoadPlugin loads a plugin from the specified path. It sends a command to the Node.js runtime to load the plugin using require().

func (*JSPluginLoader) LoadPluginSync

func (pl *JSPluginLoader) LoadPluginSync(path, currentDirectory string, context map[string]any, environment any, fileManager any) any

LoadPluginSync synchronously loads a plugin from the specified path. IMPORTANT: We always call Node.js even for cached plugins, because plugin functions need to be registered in the CURRENT scope. The scope changes during evaluation (e.g., when entering mixins), so each @plugin directive needs to register its functions at the current scope depth.

type JSPostProcessor

type JSPostProcessor struct {
	Index    int
	Priority int
	// contains filtered or unexported fields
}

JSPostProcessor wraps a JavaScript post-processor registered by a plugin. Post-processors transform CSS output after compilation.

func NewJSPostProcessor

func NewJSPostProcessor(runtime *NodeJSRuntime, index, priority int) *JSPostProcessor

NewJSPostProcessor creates a new JSPostProcessor wrapper.

func (*JSPostProcessor) Process

func (p *JSPostProcessor) Process(css string, options map[string]any) (string, error)

Process runs the post-processor on the CSS output. It sends the CSS to Node.js for processing and returns the transformed result.

type JSPreProcessor

type JSPreProcessor struct {
	Index    int
	Priority int
	// contains filtered or unexported fields
}

JSPreProcessor wraps a JavaScript pre-processor registered by a plugin. Pre-processors transform source code before parsing.

func NewJSPreProcessor

func NewJSPreProcessor(runtime *NodeJSRuntime, index, priority int) *JSPreProcessor

NewJSPreProcessor creates a new JSPreProcessor wrapper.

func (*JSPreProcessor) Process

func (p *JSPreProcessor) Process(input string, options map[string]any) (string, error)

Process runs the pre-processor on the input source code. It sends the source to Node.js for processing and returns the transformed result.

type JSResultNode

type JSResultNode struct {
	NodeType   string
	Properties map[string]any
}

JSResultNode represents a result from a JavaScript function. It implements common node interfaces so it can be used by the Go evaluator.

func (*JSResultNode) GenCSS

func (n *JSResultNode) GenCSS(context any, output interface {
	Add(string, any, any)
})

GenCSS generates CSS output for the node.

func (*JSResultNode) GetType

func (n *JSResultNode) GetType() string

GetType returns the node type.

func (*JSResultNode) GetValue

func (n *JSResultNode) GetValue() any

GetValue returns the node's value property.

func (*JSResultNode) ToCSS

func (n *JSResultNode) ToCSS() string

ToCSS returns a CSS string representation.

type JSVisitor

type JSVisitor struct {
	Index            int
	IsPreEvalVisitor bool
	IsReplacing      bool
	// contains filtered or unexported fields
}

JSVisitor wraps a JavaScript visitor registered by a plugin. It provides methods to invoke the visitor on Go AST nodes.

func NewJSVisitor

func NewJSVisitor(runtime *NodeJSRuntime, info VisitorInfo) *JSVisitor

NewJSVisitor creates a new JSVisitor wrapper.

func (*JSVisitor) Visit

func (v *JSVisitor) Visit(node interface{}) (*VisitorResult, error)

Visit runs the visitor on a Go AST node. It serializes the AST to a buffer, sends it to Node.js, runs the visitor, and returns any modifications.

type LoadedFile

type LoadedFile struct {
	Filename string `json:"filename"`
	Contents string `json:"contents"`
}

LoadedFile represents the result of loading a file.

type Node

type Node struct {
	Parent           *Node
	VisibilityBlocks *int
	NodeVisible      *bool
	RootNode         *Node
	Parsed           any
	Value            any
	Index            int

	Parens     bool
	ParensInOp bool
	TypeIndex  int
	// contains filtered or unexported fields
}

Node represents the base node structure (simplified for serialization)

type NodeJSRuntime

type NodeJSRuntime struct {
	// contains filtered or unexported fields
}

NodeJSRuntime manages a Node.js process for JavaScript plugin execution.

func NewNodeJSRuntime

func NewNodeJSRuntime(opts ...RuntimeOption) (*NodeJSRuntime, error)

NewNodeJSRuntime creates a new Node.js runtime instance.

The runtime is not started automatically. Call Start() to spawn the Node.js process.

func (*NodeJSRuntime) AttachBuffer

func (rt *NodeJSRuntime) AttachBuffer(shm *SharedMemory) error

AttachBuffer sends a command to Node.js to attach to a shared memory buffer. Returns the path to the shared memory file for Node.js to map.

func (*NodeJSRuntime) BatchCallFunctions

func (rt *NodeJSRuntime) BatchCallFunctions(calls []BatchCall) (map[string]BatchCallResult, error)

BatchCallFunctions sends multiple function calls to Node.js in a single IPC request. This reduces the overhead of multiple round-trips for plugin function calls.

The function returns a map of results keyed by the BatchCall.Key field. Each result contains Success, Result, and Error fields.

Example usage:

calls := []BatchCall{
    {Key: "map-get:colors|primary", Name: "map-get", Args: [...]},
    {Key: "color-yiq:#fff", Name: "color-yiq", Args: [...]},
}
results, err := rt.BatchCallFunctions(calls)
// results["map-get:colors|primary"].Result contains the result

func (*NodeJSRuntime) BatchCallFunctionsAndCache

func (rt *NodeJSRuntime) BatchCallFunctionsAndCache(calls []BatchCall) (int, error)

BatchCallFunctionsAndCache sends multiple function calls to Node.js in a single IPC request and caches all successful results. This is the most efficient way to warm up the function result cache for plugin functions.

Returns the number of successfully cached results and any error.

func (*NodeJSRuntime) CallFunctionViaSHM

func (rt *NodeJSRuntime) CallFunctionViaSHM(functionName string, args ...any) (any, error)

CallFunctionViaSHM calls a JavaScript function using the binary shared memory protocol. This is much faster than JSON-based IPC for repeated function calls.

func (*NodeJSRuntime) ClearCachedResultsForFunction

func (rt *NodeJSRuntime) ClearCachedResultsForFunction(funcName string)

ClearCachedResultsForFunction clears cached results for a specific function. The cache key format is "funcName:arg1|arg2|...", so this deletes all entries that start with the function name followed by a colon.

func (*NodeJSRuntime) ClearFunctionCache

func (rt *NodeJSRuntime) ClearFunctionCache()

ClearFunctionCache clears all cached function results. Call this between compilations to ensure fresh results.

func (*NodeJSRuntime) CloseSHMProtocol

func (rt *NodeJSRuntime) CloseSHMProtocol() error

CloseSHMProtocol closes the shared memory protocol and releases resources.

func (*NodeJSRuntime) CreateSharedMemory

func (rt *NodeJSRuntime) CreateSharedMemory(size int) (*SharedMemory, error)

CreateSharedMemory creates a new shared memory segment of the specified size.

func (*NodeJSRuntime) DecrementScopeDepth

func (rt *NodeJSRuntime) DecrementScopeDepth()

DecrementScopeDepth decreases the scope depth by 1 (if > 0).

func (*NodeJSRuntime) DestroySharedMemory

func (rt *NodeJSRuntime) DestroySharedMemory(shm *SharedMemory) error

DestroySharedMemory destroys a shared memory segment by key.

func (*NodeJSRuntime) DetachBuffer

func (rt *NodeJSRuntime) DetachBuffer(key string) error

DetachBuffer sends a command to Node.js to detach from a shared memory buffer.

func (*NodeJSRuntime) Echo

func (rt *NodeJSRuntime) Echo(value any) (any, error)

Echo sends a value to Node.js and expects it back (for testing).

func (*NodeJSRuntime) Error

func (rt *NodeJSRuntime) Error() error

Error returns any error from the runtime's background operations.

func (*NodeJSRuntime) FunctionCacheSize

func (rt *NodeJSRuntime) FunctionCacheSize() int

FunctionCacheSize returns the number of cached function results.

func (*NodeJSRuntime) GetCachedResult

func (rt *NodeJSRuntime) GetCachedResult(key string) (any, bool)

GetCachedResult retrieves a cached function result by key. Returns the cached value and true if found, or nil and false if not cached.

func (*NodeJSRuntime) GetPrefetchBuffer

func (rt *NodeJSRuntime) GetPrefetchBuffer(size int) (*SharedMemory, error)

GetPrefetchBuffer returns a reusable shared memory buffer for prefetch data. The buffer is created on first use and reused across calls. If the required size is larger than the current buffer, it's resized.

func (*NodeJSRuntime) GetPrefetchCache

func (rt *NodeJSRuntime) GetPrefetchCache(frameCount int, frameHash uint64) []byte

GetPrefetchCache returns the cached prefetch binary data if it's still valid for the given frames. Returns nil if the cache is invalid or doesn't exist.

The cache is considered valid if: 1. It exists 2. The frame count matches 3. The frame hash matches (detecting structural changes)

func (*NodeJSRuntime) GetPrefetchCacheStats

func (rt *NodeJSRuntime) GetPrefetchCacheStats() (int, int, bool)

GetPrefetchCacheStats returns statistics about the prefetch cache. Returns (binarySize, varCount, isValid).

func (*NodeJSRuntime) GetSHMProtocol

func (rt *NodeJSRuntime) GetSHMProtocol() *SharedMemoryProtocol

GetSHMProtocol returns the shared memory protocol if initialized.

func (*NodeJSRuntime) GetScopeDepth

func (rt *NodeJSRuntime) GetScopeDepth() int

GetScopeDepth returns the current plugin scope depth.

func (*NodeJSRuntime) GetScopeSeq

func (rt *NodeJSRuntime) GetScopeSeq() int64

GetScopeSeq returns the current scope sequence number.

func (*NodeJSRuntime) IncrementScopeDepth

func (rt *NodeJSRuntime) IncrementScopeDepth()

IncrementScopeDepth increases the scope depth by 1.

func (*NodeJSRuntime) IncrementScopeSeq

func (rt *NodeJSRuntime) IncrementScopeSeq() int64

IncrementScopeSeq increments the scope sequence number and returns the new value. This should be called whenever EnterScope is called.

func (*NodeJSRuntime) InitSHMProtocol

func (rt *NodeJSRuntime) InitSHMProtocol() error

InitSHMProtocol initializes the high-performance shared memory protocol. This creates a persistent 4MB shared memory region for binary IPC. Call this once at the start of compilation for maximum performance.

func (*NodeJSRuntime) InvalidatePrefetchCache

func (rt *NodeJSRuntime) InvalidatePrefetchCache()

InvalidatePrefetchCache explicitly invalidates the prefetch cache. Call this when you know the evaluation context has changed significantly.

func (*NodeJSRuntime) IsAlive

func (rt *NodeJSRuntime) IsAlive() bool

IsAlive returns true if the Node.js process is running.

func (*NodeJSRuntime) Ping

func (rt *NodeJSRuntime) Ping() error

Ping sends a ping command to verify the Node.js process is responsive.

func (*NodeJSRuntime) PreloadVariables

func (rt *NodeJSRuntime) PreloadVariables(frames []any) error

PreloadVariables writes all variables from the evaluation context to shared memory. This should be called once at the start of compilation for best performance.

func (*NodeJSRuntime) ReadASTBuffer

func (rt *NodeJSRuntime) ReadASTBuffer(shm *SharedMemory) (*FlatAST, error)

ReadASTBuffer reads a FlatAST from a shared memory segment. This reads the AST data that was written by Node.js.

func (*NodeJSRuntime) RegisterCallback

func (rt *NodeJSRuntime) RegisterCallback(name string, handler CallbackHandler)

RegisterCallback registers a callback handler for a specific callback type.

func (*NodeJSRuntime) SendCommand

func (rt *NodeJSRuntime) SendCommand(cmd Command) (Response, error)

SendCommand sends a command to the Node.js process and waits for a response.

func (*NodeJSRuntime) SendCommandFireAndForget

func (rt *NodeJSRuntime) SendCommandFireAndForget(cmd Command) error

SendCommandFireAndForget sends a command without waiting for a response. This is useful for commands that don't need a response or when the response can be safely ignored. The command is still sent with an ID for logging purposes.

CRITICAL: Only use this for idempotent operations where: 1. The response is not needed by the caller 2. Order of execution is not critical 3. Failure can be tolerated or will be detected later

This dramatically reduces IPC latency for operations like scope management where we send hundreds of thousands of updates during Bootstrap4 compilation.

func (*NodeJSRuntime) SendCommandWithContext

func (rt *NodeJSRuntime) SendCommandWithContext(ctx context, cmd Command) (Response, error)

SendCommandWithContext sends a command with a context for timeout/cancellation.

func (*NodeJSRuntime) SetCachedResult

func (rt *NodeJSRuntime) SetCachedResult(key string, value any)

SetCachedResult stores a function result in the cache.

func (*NodeJSRuntime) SetPrefetchCache

func (rt *NodeJSRuntime) SetPrefetchCache(binaryData []byte, frameCount int, frameHash uint64, varCount int)

SetPrefetchCache stores the serialized prefetch binary data in the cache. The frameCount and frameHash are used for invalidation checks.

func (*NodeJSRuntime) SharedMemoryManager

func (rt *NodeJSRuntime) SharedMemoryManager() *SharedMemoryManager

SharedMemoryManager returns the shared memory manager for this runtime.

func (*NodeJSRuntime) Start

func (rt *NodeJSRuntime) Start() error

Start spawns the Node.js process and begins handling IPC.

func (*NodeJSRuntime) Stop

func (rt *NodeJSRuntime) Stop() error

Stop gracefully shuts down the Node.js process.

func (*NodeJSRuntime) UnregisterCallback

func (rt *NodeJSRuntime) UnregisterCallback(name string)

UnregisterCallback removes a callback handler.

func (*NodeJSRuntime) UseSHMProtocol

func (rt *NodeJSRuntime) UseSHMProtocol() bool

UseSHMProtocol returns whether the binary SHM protocol is enabled.

func (*NodeJSRuntime) WriteASTBuffer

func (rt *NodeJSRuntime) WriteASTBuffer(flat *FlatAST) (*SharedMemory, error)

WriteASTBuffer writes a FlatAST to shared memory and returns the segment. This enables zero-copy transfer of AST data to Node.js.

type NodeReplacement

type NodeReplacement struct {
	ParentIndex int         `json:"parentIndex"`
	ChildIndex  int         `json:"childIndex"`
	Replacement interface{} `json:"replacement"`
}

NodeReplacement represents a single node replacement in the AST.

type NodeTypeID

type NodeTypeID uint16

NodeTypeID represents a unique identifier for each AST node type.

const (
	TypeUnknown NodeTypeID = iota
	TypeAnonymous
	TypeAssignment
	TypeAtRule
	TypeAttribute
	TypeCall
	TypeColor
	TypeCombinator
	TypeComment
	TypeCondition
	TypeContainer
	TypeDeclaration
	TypeDetachedRuleset
	TypeDimension
	TypeElement
	TypeExpression
	TypeExtend
	TypeImport
	TypeJavaScript
	TypeKeyword
	TypeMedia
	TypeMixinCall
	TypeMixinDefinition
	TypeNamespaceValue
	TypeNegative
	TypeOperation
	TypeParen
	TypeProperty
	TypeQueryInParens
	TypeQuoted
	TypeRuleset
	TypeSelector
	TypeSelectorList
	TypeUnicodeDescriptor
	TypeUnit
	TypeURL
	TypeValue
	TypeVariable
	TypeVariableCall
	TypeNode // Base Node type

)

Node type constants - these map to JavaScript type names.

func GetTypeID

func GetTypeID(node any) NodeTypeID

GetTypeID returns the type ID for a node based on its GetType() method.

type ParseASTBufferResult

type ParseASTBufferResult struct {
	Version         uint32
	NodeCount       uint32
	RootIndex       uint32
	StringTableSize int
	TypeTableSize   int
}

ParseASTBufferResult contains the result of parsing an AST buffer.

type Plugin

type Plugin struct {
	Path                 string          // Resolved path to the plugin
	Filename             string          // Original filename/identifier
	Functions            []string        // Names of registered functions
	ContextFreeFunctions map[string]bool // Map of function names that are context-free (pure)
	Visitors             int             // Number of registered visitors
	PreProcessors        int             // Number of registered pre-processors
	PostProcessors       int             // Number of registered post-processors
	FileManagers         int             // Number of registered file managers
	Cached               bool            // Whether this was loaded from cache
	IPCMode              JSIPCMode       // Preferred IPC mode for this plugin's functions (json or shared-memory)
}

Plugin represents a loaded JavaScript plugin with its registered components.

func (*Plugin) IsContextFree

func (p *Plugin) IsContextFree(funcName string) bool

IsContextFree returns true if the specified function is marked as context-free.

type PluginFunctionRegistry

type PluginFunctionRegistry struct {
	// contains filtered or unexported fields
}

PluginFunctionRegistry provides a unified interface for both built-in Go functions and JavaScript plugin functions.

func NewPluginFunctionRegistry

func NewPluginFunctionRegistry(builtinRegistry any, runtime *NodeJSRuntime) *PluginFunctionRegistry

NewPluginFunctionRegistry creates a new PluginFunctionRegistry.

func (*PluginFunctionRegistry) ClearJSFunctions

func (r *PluginFunctionRegistry) ClearJSFunctions()

ClearJSFunctions removes all registered JavaScript functions.

func (*PluginFunctionRegistry) Get

func (r *PluginFunctionRegistry) Get(name string) any

Get retrieves a function definition by name. JavaScript functions take precedence over built-in functions (shadowing).

func (*PluginFunctionRegistry) GetBuiltinRegistry

func (r *PluginFunctionRegistry) GetBuiltinRegistry() any

GetBuiltinRegistry returns the underlying built-in registry.

func (*PluginFunctionRegistry) GetJSFunctionNames

func (r *PluginFunctionRegistry) GetJSFunctionNames() []string

GetJSFunctionNames returns the names of all registered JavaScript functions.

func (*PluginFunctionRegistry) HasJSFunction

func (r *PluginFunctionRegistry) HasJSFunction(name string) bool

HasJSFunction checks if a JavaScript function is registered.

func (*PluginFunctionRegistry) RefreshFromRuntime

func (r *PluginFunctionRegistry) RefreshFromRuntime() error

RefreshFromRuntime queries the Node.js runtime for registered functions and updates the registry. OPTIMIZATION: Uses GetOrCreateJSFunctionDefinition to reuse cached objects.

func (*PluginFunctionRegistry) RegisterJSFunction

func (r *PluginFunctionRegistry) RegisterJSFunction(name string)

RegisterJSFunction registers a JavaScript function by name. OPTIMIZATION: Uses GetOrCreateJSFunctionDefinition to reuse cached objects.

func (*PluginFunctionRegistry) RegisterJSFunctions

func (r *PluginFunctionRegistry) RegisterJSFunctions(names []string)

RegisterJSFunctions registers multiple JavaScript functions by name. OPTIMIZATION: Uses GetOrCreateJSFunctionDefinition to reuse cached objects.

type PluginLoadResult

type PluginLoadResult struct {
	Success              bool     `json:"success"`
	Error                string   `json:"error,omitempty"`
	Cached               bool     `json:"cached"`
	Functions            []string `json:"functions,omitempty"`
	ContextFreeFunctions []string `json:"contextFreeFunctions,omitempty"` // Functions that don't need context
	Visitors             int      `json:"visitors,omitempty"`
	PreProcessors        int      `json:"preProcessors,omitempty"`
	PostProcessors       int      `json:"postProcessors,omitempty"`
	FileManagers         int      `json:"fileManagers,omitempty"`
	IPCMode              string   `json:"ipcMode,omitempty"` // "json" or "shm" - preferred IPC mode
}

PluginLoadResult contains the result of loading a plugin via Node.js.

type PluginScope

type PluginScope struct {
	// contains filtered or unexported fields
}

PluginScope represents a scope in the plugin hierarchy. It manages functions, visitors, and other plugin components that are scoped to a particular level in the LESS AST (e.g., file-level, ruleset-level, mixin-level).

Plugin scoping follows these rules: - Global plugins (@plugin at file root) affect the entire file - Local plugins (@plugin inside rulesets) only affect that scope and children - Child scopes can shadow parent functions (local overrides global) - Visitors from parent scopes are inherited

func NewPluginScope

func NewPluginScope(parent *PluginScope) *PluginScope

NewPluginScope creates a new plugin scope with an optional parent. If parent is nil, this is a root (global) scope.

OPTIMIZATION: Uses sync.Pool to reuse PluginScope objects. Call Release() when the scope is no longer needed to return it to the pool.

func NewRootPluginScope

func NewRootPluginScope() *PluginScope

NewRootPluginScope creates a root (global) plugin scope.

func (*PluginScope) AddFileManager

func (ps *PluginScope) AddFileManager(manager any)

AddFileManager adds a file manager to this scope.

func (*PluginScope) AddFunction

func (ps *PluginScope) AddFunction(name string, fn *JSFunctionDefinition)

AddFunction registers a function in this scope. This allows a local plugin to shadow a function from a parent scope.

func (*PluginScope) AddPlugin

func (ps *PluginScope) AddPlugin(plugin *Plugin, runtime *NodeJSRuntime)

AddPlugin registers a plugin in this scope. It extracts the plugin's functions and visitors and registers them locally.

OPTIMIZATION: Uses GetOrCreateJSFunctionDefinition to reuse cached objects. This eliminates redundant object allocations by reusing the same JSFunctionDefinition object for each (runtime, name) pair across all scopes.

Per-plugin IPC mode: Each plugin can specify its preferred IPC mode (JSON or SHM). This allows plugins to optimize for their specific use case: - JSON: Better for plugins with many small function calls (lower per-call overhead) - SHM: Better for plugins with large data transfers (less serialization)

Context-free functions: Plugins can declare functions as context-free (pure). These functions don't need access to LESS variables and skip context serialization.

func (*PluginScope) AddPostProcessor

func (ps *PluginScope) AddPostProcessor(processor any, priority int)

AddPostProcessor adds a post-processor to this scope with the given priority.

func (*PluginScope) AddPreProcessor

func (ps *PluginScope) AddPreProcessor(processor any, priority int)

AddPreProcessor adds a pre-processor to this scope with the given priority.

func (*PluginScope) AddVisitor

func (ps *PluginScope) AddVisitor(visitor *JSVisitor)

AddVisitor registers a visitor in this scope.

func (*PluginScope) CreateChild

func (ps *PluginScope) CreateChild() *PluginScope

CreateChild creates a new child scope with this scope as its parent. This is used when entering a new scoping boundary (e.g., a ruleset with @plugin).

func (*PluginScope) GetAllFunctions

func (ps *PluginScope) GetAllFunctions() map[string]*JSFunctionDefinition

GetAllFunctions returns all functions visible from this scope (including inherited).

func (*PluginScope) GetFileManagers

func (ps *PluginScope) GetFileManagers() []any

GetFileManagers returns all file managers from this scope and parents.

func (*PluginScope) GetLocalFunction

func (ps *PluginScope) GetLocalFunction(name string) (*JSFunctionDefinition, bool)

GetLocalFunction returns a function only if it's defined in this scope (not inherited).

func (*PluginScope) GetLocalVisitors

func (ps *PluginScope) GetLocalVisitors() []*JSVisitor

GetLocalVisitors returns only visitors defined in this scope (not inherited).

func (*PluginScope) GetPlugins

func (ps *PluginScope) GetPlugins() []*Plugin

GetPlugins returns all plugins registered in this scope.

func (*PluginScope) GetPostEvalVisitors

func (ps *PluginScope) GetPostEvalVisitors() []*JSVisitor

GetPostEvalVisitors returns all post-evaluation visitors from this scope and parents.

func (*PluginScope) GetPostProcessors

func (ps *PluginScope) GetPostProcessors() []any

GetPostProcessors returns all post-processors from this scope and parents, sorted by priority.

func (*PluginScope) GetPreEvalVisitors

func (ps *PluginScope) GetPreEvalVisitors() []*JSVisitor

GetPreEvalVisitors returns all pre-evaluation visitors from this scope and parents.

func (*PluginScope) GetPreProcessors

func (ps *PluginScope) GetPreProcessors() []any

GetPreProcessors returns all pre-processors from this scope and parents, sorted by priority.

func (*PluginScope) GetVisitors

func (ps *PluginScope) GetVisitors() []*JSVisitor

GetVisitors returns all visitors from this scope and all parent scopes. Parent visitors are included because they should still run on child content.

func (*PluginScope) IsRoot

func (ps *PluginScope) IsRoot() bool

IsRoot returns true if this is a root (global) scope.

func (*PluginScope) LookupFunction

func (ps *PluginScope) LookupFunction(name string) (*JSFunctionDefinition, bool)

LookupFunction looks up a function by name, searching from this scope up to the root. Local functions shadow parent functions with the same name. Returns the function and true if found, nil and false otherwise.

func (*PluginScope) Parent

func (ps *PluginScope) Parent() *PluginScope

Parent returns the parent scope, or nil if this is the root.

func (*PluginScope) Release

func (ps *PluginScope) Release()

Release returns the scope to the pool for reuse. The scope should not be used after calling Release(). This clears references to prevent memory leaks and returns the scope to the pool.

IMPORTANT: Only call Release() on child scopes that are no longer needed. Do not release scopes that may still be referenced elsewhere.

type PrefetchCache

type PrefetchCache struct {
	// contains filtered or unexported fields
}

PrefetchCache caches the serialized binary prefetch buffer for plugin function calls. This avoids the overhead of collecting and serializing ~39 variables on every call when the evaluation context (frames) hasn't changed.

type ProcessorInfo

type ProcessorInfo struct {
	Index    int `json:"index"`
	Priority int `json:"priority"`
}

ProcessorInfo contains metadata about a registered JavaScript processor.

type ProcessorManager

type ProcessorManager struct {
	// contains filtered or unexported fields
}

ProcessorManager manages JavaScript pre/post processors for a plugin loader.

func NewProcessorManager

func NewProcessorManager(runtime *NodeJSRuntime) *ProcessorManager

NewProcessorManager creates a new processor manager.

func (*ProcessorManager) GetPostProcessors

func (pm *ProcessorManager) GetPostProcessors() []*JSPostProcessor

GetPostProcessors returns all registered post-processors.

func (*ProcessorManager) GetPreProcessors

func (pm *ProcessorManager) GetPreProcessors() []*JSPreProcessor

GetPreProcessors returns all registered pre-processors.

func (*ProcessorManager) PostProcessorCount

func (pm *ProcessorManager) PostProcessorCount() int

PostProcessorCount returns the number of registered post-processors.

func (*ProcessorManager) PreProcessorCount

func (pm *ProcessorManager) PreProcessorCount() int

PreProcessorCount returns the number of registered pre-processors.

func (*ProcessorManager) RefreshProcessors

func (pm *ProcessorManager) RefreshProcessors() error

RefreshProcessors fetches the current list of registered processors from Node.js.

func (*ProcessorManager) RunPostProcessors

func (pm *ProcessorManager) RunPostProcessors(css string, options map[string]any) (string, error)

RunPostProcessors runs all post-processors on the CSS output. Processors are run in order of their priority (lower priority runs first).

func (*ProcessorManager) RunPreProcessors

func (pm *ProcessorManager) RunPreProcessors(input string, options map[string]any) (string, error)

RunPreProcessors runs all pre-processors on the input source code. Processors are run in order of their priority (lower priority runs first).

type ProcessorResult

type ProcessorResult struct {
	Success bool   `json:"success"`
	Output  string `json:"output,omitempty"`
	Error   string `json:"error,omitempty"`
}

ProcessorResult contains the result of running a processor.

type ProcessorWithPriority

type ProcessorWithPriority struct {
	Processor any
	Priority  int
}

ProcessorWithPriority wraps a processor with its priority for ordering.

type Response

type Response struct {
	ID      int64  `json:"id"`
	Success bool   `json:"success"`
	Result  any    `json:"result,omitempty"`
	Error   string `json:"error,omitempty"`
}

Response represents a response from the Node.js process.

type RuntimeOption

type RuntimeOption func(*NodeJSRuntime)

RuntimeOption configures a NodeJSRuntime.

func WithNodeCommand

func WithNodeCommand(cmd string) RuntimeOption

WithNodeCommand sets the Node.js command to use (default: "node").

func WithPluginHostPath

func WithPluginHostPath(path string) RuntimeOption

WithPluginHostPath sets the path to the plugin-host.js file.

type ScopedPluginManager

type ScopedPluginManager struct {
	// contains filtered or unexported fields
}

ScopedPluginManager wraps a PluginScope and provides the interface expected by the less_go PluginManager. This allows PluginScope to be used in the existing transform_tree.go visitor loop.

func NewScopedPluginManager

func NewScopedPluginManager(scope *PluginScope, runtime *NodeJSRuntime) *ScopedPluginManager

NewScopedPluginManager creates a new scoped plugin manager.

func (*ScopedPluginManager) GetFileManagers

func (spm *ScopedPluginManager) GetFileManagers() []any

GetFileManagers returns all file managers from the scope.

func (*ScopedPluginManager) GetPostProcessors

func (spm *ScopedPluginManager) GetPostProcessors() []any

GetPostProcessors returns all post-processors from the scope.

func (*ScopedPluginManager) GetPreProcessors

func (spm *ScopedPluginManager) GetPreProcessors() []any

GetPreProcessors returns all pre-processors from the scope.

func (*ScopedPluginManager) GetVisitors

func (spm *ScopedPluginManager) GetVisitors() []any

GetVisitors returns all visitors from the scope.

func (*ScopedPluginManager) Visitor

Visitor returns a visitor iterator for transform_tree.go compatibility.

type ScopedVisitorIterator

type ScopedVisitorIterator struct {
	// contains filtered or unexported fields
}

ScopedVisitorIterator provides visitor iteration for transform_tree.go.

func (*ScopedVisitorIterator) First

func (vi *ScopedVisitorIterator) First()

First resets the iterator to the beginning.

func (*ScopedVisitorIterator) Get

func (vi *ScopedVisitorIterator) Get() any

Get returns the next visitor in the iteration.

type SerializeNodeResult

type SerializeNodeResult struct {
	Buffer []byte
	JSON   string
	Size   int
}

SerializeNodeResult contains the result of serializing a node.

type SharedMemory

type SharedMemory struct {
	// contains filtered or unexported fields
}

SharedMemory represents a shared memory segment backed by a memory-mapped file. This provides zero-copy data transfer between Go and Node.js processes.

func (*SharedMemory) Close

func (s *SharedMemory) Close() error

Close unmaps the memory and closes the file.

func (*SharedMemory) Data

func (s *SharedMemory) Data() []byte

Data returns the underlying byte slice (read-only copy).

func (*SharedMemory) Key

func (s *SharedMemory) Key() string

Key returns the unique identifier for this segment.

func (*SharedMemory) Path

func (s *SharedMemory) Path() string

Path returns the file path for this segment.

func (*SharedMemory) Read

func (s *SharedMemory) Read(offset, length int) ([]byte, error)

Read reads data from the shared memory segment.

func (*SharedMemory) ReadAll

func (s *SharedMemory) ReadAll() ([]byte, error)

ReadAll reads all data from the shared memory segment.

func (*SharedMemory) Size

func (s *SharedMemory) Size() int

Size returns the size of the mapped region.

func (*SharedMemory) Sync

func (s *SharedMemory) Sync() error

Sync flushes any cached writes to the backing file. This is critical for IPC with processes that read the file directly instead of memory-mapping it (like Node.js using fs.readFileSync).

func (*SharedMemory) Write

func (s *SharedMemory) Write(offset int, data []byte) error

Write writes data to the shared memory segment at the specified offset.

func (*SharedMemory) WriteAll

func (s *SharedMemory) WriteAll(data []byte) error

WriteAll writes data to the beginning of the segment.

type SharedMemoryManager

type SharedMemoryManager struct {
	// contains filtered or unexported fields
}

SharedMemoryManager manages shared memory segments for a runtime.

func NewSharedMemoryManager

func NewSharedMemoryManager() (*SharedMemoryManager, error)

NewSharedMemoryManager creates a new shared memory manager.

func (*SharedMemoryManager) Create

func (m *SharedMemoryManager) Create(size int) (*SharedMemory, error)

Create creates a new shared memory segment of the specified size.

func (*SharedMemoryManager) Destroy

func (m *SharedMemoryManager) Destroy(key string) error

Destroy destroys a shared memory segment.

func (*SharedMemoryManager) DestroyAll

func (m *SharedMemoryManager) DestroyAll() error

DestroyAll destroys all shared memory segments and cleans up.

func (*SharedMemoryManager) Get

Get retrieves a shared memory segment by key.

func (*SharedMemoryManager) Open

func (m *SharedMemoryManager) Open(key string) (*SharedMemory, error)

Open opens an existing shared memory segment by key (read-only).

type SharedMemoryProtocol

type SharedMemoryProtocol struct {
	// contains filtered or unexported fields
}

SharedMemoryProtocol manages a persistent shared memory region for high-performance IPC between Go and Node.js.

func NewSharedMemoryProtocol

func NewSharedMemoryProtocol(shmManager *SharedMemoryManager) (*SharedMemoryProtocol, error)

NewSharedMemoryProtocol creates a new shared memory protocol instance.

func (*SharedMemoryProtocol) ClearResponse

func (p *SharedMemoryProtocol) ClearResponse()

ClearResponse clears the response ready flag for the next call.

func (*SharedMemoryProtocol) Close

func (p *SharedMemoryProtocol) Close() error

Close releases the shared memory resources.

func (*SharedMemoryProtocol) GetArgsSectionInfo

func (p *SharedMemoryProtocol) GetArgsSectionInfo() (offset, size uint32)

GetArgsSectionInfo returns info about the args section for JS.

func (*SharedMemoryProtocol) GetControlBlockLayout

func (p *SharedMemoryProtocol) GetControlBlockLayout() map[string]uint32

GetControlBlockLayout returns the control block layout for JS initialization.

func (*SharedMemoryProtocol) GetFunctionID

func (p *SharedMemoryProtocol) GetFunctionID(name string) (uint32, bool)

GetFunctionID returns the ID for a function name.

func (*SharedMemoryProtocol) GetFunctionName

func (p *SharedMemoryProtocol) GetFunctionName(id uint32) (string, bool)

GetFunctionName returns the name for a function ID.

func (*SharedMemoryProtocol) GetResultsSectionInfo

func (p *SharedMemoryProtocol) GetResultsSectionInfo() (offset, size uint32)

GetResultsSectionInfo returns info about the results section for JS.

func (*SharedMemoryProtocol) GetSectionOffsets

func (p *SharedMemoryProtocol) GetSectionOffsets() map[string]uint32

GetSectionOffsets returns all section offsets for JS.

func (*SharedMemoryProtocol) GetVariablesSectionInfo

func (p *SharedMemoryProtocol) GetVariablesSectionInfo() (offset, size uint32)

GetVariablesSectionInfo returns info about the variables section for JS.

func (*SharedMemoryProtocol) IsJSReady

func (p *SharedMemoryProtocol) IsJSReady() bool

IsJSReady checks if JavaScript has signaled it's ready.

func (*SharedMemoryProtocol) Key

func (p *SharedMemoryProtocol) Key() string

Key returns the unique key for this shared memory segment.

func (*SharedMemoryProtocol) Path

func (p *SharedMemoryProtocol) Path() string

Path returns the path to the shared memory file.

func (*SharedMemoryProtocol) PrepareCall

func (p *SharedMemoryProtocol) PrepareCall(functionID uint32, argCount int) error

PrepareCall prepares a function call in shared memory. Returns the offset where arguments should be written.

func (*SharedMemoryProtocol) ReadResult

func (p *SharedMemoryProtocol) ReadResult() (any, error)

ReadResult reads the result from the results section.

func (*SharedMemoryProtocol) RegisterFunction

func (p *SharedMemoryProtocol) RegisterFunction(name string) uint32

RegisterFunction registers a function and returns its ID.

func (*SharedMemoryProtocol) SignalRequest

func (p *SharedMemoryProtocol) SignalRequest() error

SignalRequest signals that the request is ready.

func (*SharedMemoryProtocol) WaitForResponse

func (p *SharedMemoryProtocol) WaitForResponse(maxWaitMs int) (bool, error)

WaitForResponse waits for the JavaScript side to signal completion. This uses busy-waiting with file re-reads to detect changes from Node.js.

func (*SharedMemoryProtocol) WriteArg

func (p *SharedMemoryProtocol) WriteArg(argIndex int, value any) (uint32, error)

WriteArg writes an argument to the arguments section. Returns the offset where the argument was written.

func (*SharedMemoryProtocol) WriteVariables

func (p *SharedMemoryProtocol) WriteVariables(variables map[string]any) error

WriteVariables writes all variables to the variables section in binary format. This should be called once at the start of compilation.

type UnifiedMessage

type UnifiedMessage struct {
	ID       int64  `json:"id"`
	Success  bool   `json:"success"`
	Result   any    `json:"result,omitempty"`
	Error    string `json:"error,omitempty"`
	Callback string `json:"callback,omitempty"` // Only set for callback requests
	Data     any    `json:"data,omitempty"`     // Only set for callback requests
}

UnifiedMessage is used for single-pass JSON parsing of IPC messages. It can represent either a Response or a CallbackRequest.

type VisitorInfo

type VisitorInfo struct {
	Index            int  `json:"index"`
	IsPreEvalVisitor bool `json:"isPreEvalVisitor"`
	IsReplacing      bool `json:"isReplacing"`
}

VisitorInfo contains metadata about a registered JavaScript visitor.

type VisitorManager

type VisitorManager struct {
	// contains filtered or unexported fields
}

VisitorManager manages JavaScript visitors for a plugin loader.

func NewVisitorManager

func NewVisitorManager(runtime *NodeJSRuntime) *VisitorManager

NewVisitorManager creates a new visitor manager.

func (*VisitorManager) GetPostEvalVisitors

func (vm *VisitorManager) GetPostEvalVisitors() []*JSVisitor

GetPostEvalVisitors returns all post-evaluation visitors.

func (*VisitorManager) GetPreEvalVisitors

func (vm *VisitorManager) GetPreEvalVisitors() []*JSVisitor

GetPreEvalVisitors returns all pre-evaluation visitors.

func (*VisitorManager) ParseASTBuffer

func (vm *VisitorManager) ParseASTBuffer(shm *SharedMemory) (*ParseASTBufferResult, error)

ParseASTBuffer parses an AST buffer and returns metadata.

func (*VisitorManager) RefreshVisitors

func (vm *VisitorManager) RefreshVisitors() error

RefreshVisitors fetches the current list of registered visitors from Node.js.

func (*VisitorManager) RunPostEvalVisitors

func (vm *VisitorManager) RunPostEvalVisitors(node interface{}) (*VisitorResult, error)

RunPostEvalVisitors runs all post-evaluation visitors on an AST.

func (*VisitorManager) RunPreEvalVisitors

func (vm *VisitorManager) RunPreEvalVisitors(node interface{}) (*VisitorResult, error)

RunPreEvalVisitors runs all pre-evaluation visitors on an AST.

func (*VisitorManager) SerializeNode

func (vm *VisitorManager) SerializeNode(node interface{}) (*SerializeNodeResult, error)

SerializeNode serializes a JavaScript node to buffer format.

type VisitorReplacementSet

type VisitorReplacementSet struct {
	VisitorIndex int               `json:"visitorIndex"`
	Replacements []NodeReplacement `json:"replacements"`
}

VisitorReplacementSet contains replacements from a single visitor.

type VisitorResult

type VisitorResult struct {
	Success      bool                    `json:"success"`
	Replacements []VisitorReplacementSet `json:"replacements,omitempty"`
	VisitorCount int                     `json:"visitorCount,omitempty"`
	ResultType   string                  `json:"resultType,omitempty"`
	Message      string                  `json:"message,omitempty"`
}

VisitorResult contains the result of running a visitor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL