@jesscss/plugin-less-compat 2.0.0-alpha.1 → 2.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -115
- package/lib/index.cjs +342 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +332 -9
- package/lib/less-api-bridge.d.ts +85 -0
- package/lib/plugin.d.ts +21 -82
- package/package.json +14 -19
- package/lib/index.js.map +0 -1
- package/lib/less-compat-structures.d.ts +0 -108
- package/lib/less-compat-structures.js +0 -519
- package/lib/less-compat-structures.js.map +0 -1
- package/lib/nodes/at-rule.d.ts +0 -6
- package/lib/nodes/at-rule.js +0 -72
- package/lib/nodes/at-rule.js.map +0 -1
- package/lib/nodes/attribute-selector.d.ts +0 -6
- package/lib/nodes/attribute-selector.js +0 -54
- package/lib/nodes/attribute-selector.js.map +0 -1
- package/lib/nodes/call.d.ts +0 -6
- package/lib/nodes/call.js +0 -83
- package/lib/nodes/call.js.map +0 -1
- package/lib/nodes/color.d.ts +0 -6
- package/lib/nodes/color.js +0 -57
- package/lib/nodes/color.js.map +0 -1
- package/lib/nodes/combinator.d.ts +0 -6
- package/lib/nodes/combinator.js +0 -34
- package/lib/nodes/combinator.js.map +0 -1
- package/lib/nodes/comment.d.ts +0 -6
- package/lib/nodes/comment.js +0 -41
- package/lib/nodes/comment.js.map +0 -1
- package/lib/nodes/condition.d.ts +0 -6
- package/lib/nodes/condition.js +0 -60
- package/lib/nodes/condition.js.map +0 -1
- package/lib/nodes/declaration.d.ts +0 -6
- package/lib/nodes/declaration.js +0 -81
- package/lib/nodes/declaration.js.map +0 -1
- package/lib/nodes/dimension.d.ts +0 -6
- package/lib/nodes/dimension.js +0 -47
- package/lib/nodes/dimension.js.map +0 -1
- package/lib/nodes/expression.d.ts +0 -6
- package/lib/nodes/expression.js +0 -44
- package/lib/nodes/expression.js.map +0 -1
- package/lib/nodes/extend.d.ts +0 -6
- package/lib/nodes/extend.js +0 -57
- package/lib/nodes/extend.js.map +0 -1
- package/lib/nodes/import.d.ts +0 -6
- package/lib/nodes/import.js +0 -63
- package/lib/nodes/import.js.map +0 -1
- package/lib/nodes/index.d.ts +0 -45
- package/lib/nodes/index.js +0 -308
- package/lib/nodes/index.js.map +0 -1
- package/lib/nodes/keyword.d.ts +0 -6
- package/lib/nodes/keyword.js +0 -36
- package/lib/nodes/keyword.js.map +0 -1
- package/lib/nodes/list.d.ts +0 -6
- package/lib/nodes/list.js +0 -150
- package/lib/nodes/list.js.map +0 -1
- package/lib/nodes/mixin.d.ts +0 -6
- package/lib/nodes/mixin.js +0 -62
- package/lib/nodes/mixin.js.map +0 -1
- package/lib/nodes/negative.d.ts +0 -6
- package/lib/nodes/negative.js +0 -42
- package/lib/nodes/negative.js.map +0 -1
- package/lib/nodes/operation.d.ts +0 -6
- package/lib/nodes/operation.js +0 -63
- package/lib/nodes/operation.js.map +0 -1
- package/lib/nodes/paren.d.ts +0 -6
- package/lib/nodes/paren.js +0 -42
- package/lib/nodes/paren.js.map +0 -1
- package/lib/nodes/quoted.d.ts +0 -6
- package/lib/nodes/quoted.js +0 -57
- package/lib/nodes/quoted.js.map +0 -1
- package/lib/nodes/reference.d.ts +0 -9
- package/lib/nodes/reference.js +0 -80
- package/lib/nodes/reference.js.map +0 -1
- package/lib/nodes/ruleset.d.ts +0 -6
- package/lib/nodes/ruleset.js +0 -108
- package/lib/nodes/ruleset.js.map +0 -1
- package/lib/nodes/selector.d.ts +0 -8
- package/lib/nodes/selector.js +0 -226
- package/lib/nodes/selector.js.map +0 -1
- package/lib/nodes/sequence.d.ts +0 -9
- package/lib/nodes/sequence.js +0 -75
- package/lib/nodes/sequence.js.map +0 -1
- package/lib/nodes/url.d.ts +0 -6
- package/lib/nodes/url.js +0 -42
- package/lib/nodes/url.js.map +0 -1
- package/lib/nodes/var-declaration.d.ts +0 -6
- package/lib/nodes/var-declaration.js +0 -60
- package/lib/nodes/var-declaration.js.map +0 -1
- package/lib/plugin-utils.d.ts +0 -20
- package/lib/plugin-utils.js +0 -100
- package/lib/plugin-utils.js.map +0 -1
- package/lib/plugin.js +0 -1027
- package/lib/plugin.js.map +0 -1
- package/lib/transform/from-less.d.ts +0 -30
- package/lib/transform/from-less.js +0 -170
- package/lib/transform/from-less.js.map +0 -1
- package/lib/transform/index.d.ts +0 -7
- package/lib/transform/index.js +0 -8
- package/lib/transform/index.js.map +0 -1
- package/lib/transform/proxy.d.ts +0 -32
- package/lib/transform/proxy.js +0 -138
- package/lib/transform/proxy.js.map +0 -1
- package/lib/transform/to-less.d.ts +0 -17
- package/lib/transform/to-less.js +0 -128
- package/lib/transform/to-less.js.map +0 -1
- package/lib/transform/type-map.d.ts +0 -27
- package/lib/transform/type-map.js +0 -105
- package/lib/transform/type-map.js.map +0 -1
- package/lib/types.d.ts +0 -33
- package/lib/types.js +0 -5
- package/lib/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,139 +1,74 @@
|
|
|
1
1
|
# @jesscss/plugin-less-compat
|
|
2
2
|
|
|
3
|
-
Less
|
|
3
|
+
**A Less compatibility bridge for Jess.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Jess](https://github.com/jesscss/jess) accepts typed AST-v2 `Fn` values here
|
|
6
|
+
and provides the shared Jess-owned bridge for Less-style plugin function
|
|
7
|
+
registration and Less-shaped function values.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
The public compiler does **not** support Less 4 visitors, post-processors, file
|
|
10
|
+
manager plugins, or a full Less tree AST adapter.
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
- **Use at your own risk**: While it may work for some use cases, it has not been thoroughly tested across all Less.js plugins
|
|
11
|
-
- **Breaking changes likely**: The API and implementation may change or be removed without notice
|
|
12
|
-
- **Not production-ready**: This is an experimental exploration, not a production-grade compatibility layer
|
|
12
|
+
## How it works
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
14
|
+
- **Native functions only** — pass `Fn` values from `@jesscss/core/value`; their
|
|
15
|
+
bodies receive typed values and `FnCtx` capabilities.
|
|
16
|
+
- **Less plugin function bridge** — pass Less-style plugins with
|
|
17
|
+
`install(less, manager, functions)` through `plugins`. The bridge supplies a
|
|
18
|
+
Less-shaped `functions.functionRegistry` and the supported `less.tree`
|
|
19
|
+
constructor facades, then lazily adapts function arguments and return values at
|
|
20
|
+
the Jess boundary.
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
For example, migrate a Less `functionRegistry.add('increment', fn)` contribution
|
|
23
|
+
to a typed function and register it through `functions`:
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- 📦 **Type Safe**: Full TypeScript support
|
|
25
|
+
```ts
|
|
26
|
+
import { Compiler } from 'jess';
|
|
27
|
+
import { defineFunction, makeDimension } from '@jesscss/core/value';
|
|
28
|
+
import lessCompatPlugin from '@jesscss/plugin-less-compat';
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Usage
|
|
34
|
-
|
|
35
|
-
### Basic Usage with Less Plugins
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
import { Compiler } from '@jesscss/jess';
|
|
39
|
-
import lessPlugin from '@jesscss/plugin-less';
|
|
40
|
-
import { lessCompatPlugin } from '@jesscss/plugin-less-compat';
|
|
41
|
-
import autoprefix from 'less-plugin-autoprefix';
|
|
30
|
+
const increment = defineFunction('increment', {
|
|
31
|
+
params: [{ kinds: ['Dimension'] }] as const,
|
|
32
|
+
body: value => makeDimension(value.number + 1, value.unit)
|
|
33
|
+
});
|
|
42
34
|
|
|
43
35
|
const compiler = new Compiler({
|
|
44
|
-
plugins: [
|
|
45
|
-
lessPlugin(),
|
|
46
|
-
lessCompatPlugin({
|
|
47
|
-
visitors: [autoprefix]
|
|
48
|
-
})
|
|
49
|
-
]
|
|
36
|
+
compile: { plugins: [lessCompatPlugin({ functions: [increment] })] }
|
|
50
37
|
});
|
|
51
|
-
|
|
52
|
-
const result = await compiler.compile('styles.less');
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Advanced: Direct Transformation
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
import { toLessTree, fromLessTree } from '@jesscss/plugin-less-compat/transform';
|
|
59
|
-
import customLessVisitor from './custom-visitor';
|
|
60
|
-
|
|
61
|
-
const jessTree = parseJess(source);
|
|
62
|
-
const lessTree = toLessTree(jessTree);
|
|
63
|
-
|
|
64
|
-
// Apply custom Less visitor
|
|
65
|
-
lessTree.accept(customLessVisitor);
|
|
66
|
-
|
|
67
|
-
// Convert back to Jess
|
|
68
|
-
const modifiedJessTree = fromLessTree(lessTree);
|
|
69
38
|
```
|
|
70
39
|
|
|
71
|
-
|
|
40
|
+
For legacy Less function plugins:
|
|
72
41
|
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
```ts
|
|
43
|
+
const legacyPlugin = {
|
|
44
|
+
install(less, _manager, functions) {
|
|
45
|
+
functions.add('increment', value =>
|
|
46
|
+
new less.tree.Dimension(value.value + 1, value.unit)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
77
50
|
|
|
78
51
|
const compiler = new Compiler({
|
|
79
|
-
plugins: [
|
|
80
|
-
lessPlugin(),
|
|
81
|
-
lessCompatPlugin({
|
|
82
|
-
visitors: [
|
|
83
|
-
autoprefix,
|
|
84
|
-
cleanCSS
|
|
85
|
-
]
|
|
86
|
-
})
|
|
87
|
-
]
|
|
52
|
+
compile: { plugins: [lessCompatPlugin({ plugins: [legacyPlugin] })] }
|
|
88
53
|
});
|
|
89
54
|
```
|
|
90
55
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
This package has been tested with:
|
|
94
|
-
|
|
95
|
-
- ✅ `less-plugin-autoprefix` - Automatic vendor prefixing
|
|
96
|
-
- ✅ `less-plugin-clean-css` - CSS minification
|
|
97
|
-
- ✅ `less-plugin-dls` - Design Language System support
|
|
98
|
-
|
|
99
|
-
Other Less.js plugins should work, but may require additional testing.
|
|
100
|
-
|
|
101
|
-
## API
|
|
102
|
-
|
|
103
|
-
### `lessCompatPlugin(options?)`
|
|
104
|
-
|
|
105
|
-
Creates a Jess plugin that enables Less.js compatibility.
|
|
106
|
-
|
|
107
|
-
**Options:**
|
|
108
|
-
- `visitors?: LessVisitor[]` - Array of Less.js visitors to apply
|
|
109
|
-
- `cache?: boolean` - Enable conversion caching (default: `true`)
|
|
110
|
-
|
|
111
|
-
### `toLessTree(jessRules: Rules)`
|
|
112
|
-
|
|
113
|
-
Converts a Jess `Rules` tree to a Less.js-compatible tree.
|
|
114
|
-
|
|
115
|
-
### `fromLessTree(lessTree: LessNode)`
|
|
116
|
-
|
|
117
|
-
Converts a Less.js tree back to a Jess `Rules` tree.
|
|
118
|
-
|
|
119
|
-
### `toLessNode(jessNode: Node, options?)`
|
|
120
|
-
|
|
121
|
-
Converts a single Jess node to a Less.js-compatible node.
|
|
122
|
-
|
|
123
|
-
### `fromLessNode(lessNode: LessNode, options?)`
|
|
124
|
-
|
|
125
|
-
Converts a single Less.js node back to a Jess node.
|
|
126
|
-
|
|
127
|
-
## Implementation Status
|
|
128
|
-
|
|
129
|
-
This package is currently in **alpha**.
|
|
130
|
-
|
|
131
|
-
Developer notes and historical analyses live in [DESIGN.md](./DESIGN.md) and `_archive/`.
|
|
56
|
+
Less-shaped values are boundary values only. Jess owns their conversion back
|
|
57
|
+
into typed AST-v2 values.
|
|
132
58
|
|
|
133
|
-
##
|
|
59
|
+
## Status
|
|
134
60
|
|
|
135
|
-
|
|
61
|
+
**Alpha / experimental.** The supported public routes are native function
|
|
62
|
+
contribution and Less plugin function bridging. If you need Less visitors,
|
|
63
|
+
post-processors, file managers, or broad Less tree AST mutation support today,
|
|
64
|
+
use Less.js directly.
|
|
136
65
|
|
|
137
|
-
|
|
66
|
+
The programmatic plugin/compiler API is **not yet stabilized** — the `jess` CLI
|
|
67
|
+
is the documented public surface for the alpha. Watch the
|
|
68
|
+
[docs site](https://jesscss.github.io/) for the API once it settles. Current
|
|
69
|
+
design notes live in [DESIGN.md](./DESIGN.md).
|
|
138
70
|
|
|
139
|
-
|
|
71
|
+
- Project overview & positioning: <https://github.com/jesscss/jess#readme>
|
|
72
|
+
- Docs: <https://jesscss.github.io/> (currently pre-alpha content)
|
|
73
|
+
- Issues: <https://github.com/jesscss/jess/issues>
|
|
74
|
+
- License: MIT
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let _jesscss_core = require("@jesscss/core");
|
|
6
|
+
let _jesscss_core_value = require("@jesscss/core/value");
|
|
7
|
+
//#region src/less-api-bridge.ts
|
|
8
|
+
const isThenable = (value) => value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
9
|
+
function isLoadedPluginModule(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11
|
+
if (!("functions" in value) || value.functions === void 0) return true;
|
|
12
|
+
return typeof value.functions === "object" && value.functions !== null && Object.values(value.functions).every((fn) => typeof fn === "function");
|
|
13
|
+
}
|
|
14
|
+
const nativeNil = () => {
|
|
15
|
+
const nil = {
|
|
16
|
+
type: "Nil",
|
|
17
|
+
value: "",
|
|
18
|
+
eval: () => nil
|
|
19
|
+
};
|
|
20
|
+
return nil;
|
|
21
|
+
};
|
|
22
|
+
var LessDimension = class {
|
|
23
|
+
type = "Dimension";
|
|
24
|
+
constructor(value, unit = "") {
|
|
25
|
+
this.value = value;
|
|
26
|
+
this.unit = unit;
|
|
27
|
+
}
|
|
28
|
+
valueOf() {
|
|
29
|
+
return this.value;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var LessQuoted = class {
|
|
33
|
+
type = "Quoted";
|
|
34
|
+
constructor(quote, value, escaped = false) {
|
|
35
|
+
this.quote = quote;
|
|
36
|
+
this.value = value;
|
|
37
|
+
this.escaped = escaped;
|
|
38
|
+
}
|
|
39
|
+
valueOf() {
|
|
40
|
+
return this.value;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var LessColor = class {
|
|
44
|
+
type = "Color";
|
|
45
|
+
value;
|
|
46
|
+
constructor(rgb, alpha = 1) {
|
|
47
|
+
this.rgb = rgb;
|
|
48
|
+
this.alpha = alpha;
|
|
49
|
+
this.value = typeof rgb === "string" ? rgb.startsWith("#") ? rgb : `#${rgb}` : `rgb(${rgb.join(", ")})`;
|
|
50
|
+
}
|
|
51
|
+
valueOf() {
|
|
52
|
+
return this.value;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var LessAnonymous = class {
|
|
56
|
+
type = "Anonymous";
|
|
57
|
+
value;
|
|
58
|
+
constructor(value) {
|
|
59
|
+
this.value = String(value);
|
|
60
|
+
}
|
|
61
|
+
valueOf() {
|
|
62
|
+
return this.value;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var LazyExpression = class {
|
|
66
|
+
type = "Expression";
|
|
67
|
+
#value;
|
|
68
|
+
constructor(source) {
|
|
69
|
+
this.source = source;
|
|
70
|
+
}
|
|
71
|
+
get value() {
|
|
72
|
+
return this.#value ??= this.source.map(toNativeLessValue);
|
|
73
|
+
}
|
|
74
|
+
valueOf() {
|
|
75
|
+
return (0, _jesscss_core_value.emitValue)(this.source);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var LazyValueList = class {
|
|
79
|
+
type = "Value";
|
|
80
|
+
#value;
|
|
81
|
+
constructor(source) {
|
|
82
|
+
this.source = source;
|
|
83
|
+
}
|
|
84
|
+
get value() {
|
|
85
|
+
return this.#value ??= this.source.value.map(toNativeLessValue);
|
|
86
|
+
}
|
|
87
|
+
get separator() {
|
|
88
|
+
return this.source.sep;
|
|
89
|
+
}
|
|
90
|
+
valueOf() {
|
|
91
|
+
return this.source.bytes;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var LazyDetachedDeclaration = class {
|
|
95
|
+
type = "Declaration";
|
|
96
|
+
#value;
|
|
97
|
+
#hasValue = false;
|
|
98
|
+
constructor(name, source) {
|
|
99
|
+
this.name = name;
|
|
100
|
+
this.source = source;
|
|
101
|
+
}
|
|
102
|
+
get value() {
|
|
103
|
+
if (!this.#hasValue) {
|
|
104
|
+
this.#value = toNativeLessValue(this.source);
|
|
105
|
+
this.#hasValue = true;
|
|
106
|
+
}
|
|
107
|
+
return this.#value;
|
|
108
|
+
}
|
|
109
|
+
eval() {
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var LazyDetachedRuleset = class {
|
|
114
|
+
type = "Mixin";
|
|
115
|
+
name = nativeNil();
|
|
116
|
+
args = nativeNil();
|
|
117
|
+
#rules;
|
|
118
|
+
constructor(source) {
|
|
119
|
+
this.source = source;
|
|
120
|
+
}
|
|
121
|
+
get ruleset() {
|
|
122
|
+
return { rules: this.rules };
|
|
123
|
+
}
|
|
124
|
+
get rules() {
|
|
125
|
+
return this.#rules ??= this.source.rules.map((rule) => new LazyDetachedDeclaration(rule.name, rule.value));
|
|
126
|
+
}
|
|
127
|
+
eval() {
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function isRawSequence(value) {
|
|
132
|
+
return Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
function isPluginDetached(value) {
|
|
135
|
+
return !isRawSequence(value) && value.type === "DetachedRuleset";
|
|
136
|
+
}
|
|
137
|
+
function toNativeLessValue(value) {
|
|
138
|
+
if (isRawSequence(value)) return new LazyExpression(value);
|
|
139
|
+
if (isPluginDetached(value)) return new LazyDetachedRuleset(value);
|
|
140
|
+
switch (value.type) {
|
|
141
|
+
case "Dimension": return new LessDimension(value.number, value.unit);
|
|
142
|
+
case "Quoted": return new LessQuoted(value.quote, value.value, value.escaped);
|
|
143
|
+
case "Color": return {
|
|
144
|
+
type: "Color",
|
|
145
|
+
rgb: value.rgb,
|
|
146
|
+
alpha: value.alpha,
|
|
147
|
+
valueOf: () => value.bytes
|
|
148
|
+
};
|
|
149
|
+
case "List": return value.sep === "," || value.sep === "/" ? new LazyValueList(value) : new LessAnonymous(value.bytes);
|
|
150
|
+
default: return new LessAnonymous(value.bytes);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function isNativeValue(value) {
|
|
154
|
+
return value !== null && typeof value === "object" && "bytes" in value && typeof value.bytes === "string";
|
|
155
|
+
}
|
|
156
|
+
function fromNativeLessValue(value) {
|
|
157
|
+
if (isNativeValue(value)) return value;
|
|
158
|
+
if (typeof value === "number") return (0, _jesscss_core_value.makeDimension)(value);
|
|
159
|
+
if (typeof value === "string") return (0, _jesscss_core_value.sniffLiteral)(value);
|
|
160
|
+
if (value && typeof value === "object") {
|
|
161
|
+
const candidate = value;
|
|
162
|
+
if ((candidate.type === "Dimension" || candidate.type === "Num") && typeof candidate.value === "number") return (0, _jesscss_core_value.makeDimension)(candidate.value, typeof candidate.unit === "string" ? candidate.unit : "");
|
|
163
|
+
if (candidate.type === "Quoted" && typeof candidate.value === "string") return (0, _jesscss_core_value.makeQuoted)(candidate.value, candidate.quote === "'" ? "'" : "\"", candidate.escaped === true);
|
|
164
|
+
if (candidate.type === "Color") {
|
|
165
|
+
if (Array.isArray(candidate.rgb)) {
|
|
166
|
+
const [r = 0, g = 0, b = 0] = candidate.rgb.map(Number);
|
|
167
|
+
const alpha = typeof candidate.alpha === "number" ? candidate.alpha : 1;
|
|
168
|
+
return (0, _jesscss_core_value.makeColorRgb)([
|
|
169
|
+
r,
|
|
170
|
+
g,
|
|
171
|
+
b
|
|
172
|
+
], alpha, _jesscss_core_value.HEX);
|
|
173
|
+
}
|
|
174
|
+
if (typeof candidate.rgb === "string") return (0, _jesscss_core_value.sniffLiteral)(candidate.rgb.startsWith("#") ? candidate.rgb : `#${candidate.rgb}`);
|
|
175
|
+
}
|
|
176
|
+
if (candidate.type === "Expression" && Array.isArray(candidate.value)) return candidate.value.map(fromNativeLessValue);
|
|
177
|
+
if (candidate.type === "Value" && Array.isArray(candidate.value)) {
|
|
178
|
+
const separator = candidate.separator === "/" ? "/" : ",";
|
|
179
|
+
return (0, _jesscss_core_value.makeList)(candidate.value.map((item) => fromNativeLessValue(item)), separator);
|
|
180
|
+
}
|
|
181
|
+
if (typeof candidate.value === "string") return (0, _jesscss_core_value.sniffLiteral)(candidate.value);
|
|
182
|
+
if (typeof candidate.valueOf === "function") return (0, _jesscss_core_value.sniffLiteral)(String(candidate.valueOf()));
|
|
183
|
+
}
|
|
184
|
+
return (0, _jesscss_core_value.makeKeyword)(value == null ? "" : String(value));
|
|
185
|
+
}
|
|
186
|
+
var LessApiBridge = class {
|
|
187
|
+
less;
|
|
188
|
+
registry;
|
|
189
|
+
globalFns;
|
|
190
|
+
#fns = [];
|
|
191
|
+
#nativeFns = /* @__PURE__ */ new WeakMap();
|
|
192
|
+
#contextualFns = /* @__PURE__ */ new WeakMap();
|
|
193
|
+
#registered = /* @__PURE__ */ new Map();
|
|
194
|
+
constructor(plugins = []) {
|
|
195
|
+
this.registry = {
|
|
196
|
+
add: (name, fn) => {
|
|
197
|
+
this.addFunction(name, fn);
|
|
198
|
+
},
|
|
199
|
+
addMultiple: (functions) => {
|
|
200
|
+
for (const [name, fn] of Object.entries(functions)) this.registry.add(name, fn);
|
|
201
|
+
},
|
|
202
|
+
get: (name) => this.#registered.get(name.toLowerCase())
|
|
203
|
+
};
|
|
204
|
+
this.less = {
|
|
205
|
+
functions: { functionRegistry: this.registry },
|
|
206
|
+
tree: {
|
|
207
|
+
Dimension: LessDimension,
|
|
208
|
+
Quoted: LessQuoted,
|
|
209
|
+
Color: LessColor,
|
|
210
|
+
Anonymous: LessAnonymous
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
for (const plugin of plugins) plugin.install?.(this.less, void 0, this.registry);
|
|
214
|
+
this.globalFns = this.#fns;
|
|
215
|
+
}
|
|
216
|
+
addFunction(name, fn) {
|
|
217
|
+
const lowerName = name.toLowerCase();
|
|
218
|
+
this.#registered.set(lowerName, fn);
|
|
219
|
+
const adapted = (0, _jesscss_core_value.defineFunction)(lowerName, {
|
|
220
|
+
variadic: true,
|
|
221
|
+
params: [],
|
|
222
|
+
body: (value) => this.invokeNativeFunction(fn, (0, _jesscss_core_value.groupItems)(value))
|
|
223
|
+
});
|
|
224
|
+
this.#nativeFns.set(adapted, fn);
|
|
225
|
+
this.#fns.push(adapted);
|
|
226
|
+
return adapted;
|
|
227
|
+
}
|
|
228
|
+
addContextualFunction(name, fn) {
|
|
229
|
+
const adapted = (0, _jesscss_core_value.defineFunction)(name.toLowerCase(), {
|
|
230
|
+
variadic: true,
|
|
231
|
+
params: [],
|
|
232
|
+
body: () => {
|
|
233
|
+
throw new Error(`Less @plugin function "${name}" needs live plugin invocation capabilities; it cannot run through plain function dispatch.`);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
this.#contextualFns.set(adapted, fn);
|
|
237
|
+
this.#fns.push(adapted);
|
|
238
|
+
return adapted;
|
|
239
|
+
}
|
|
240
|
+
invokeNativeFunction(fn, args) {
|
|
241
|
+
const result = fn(...args.map(toNativeLessValue));
|
|
242
|
+
return isThenable(result) ? Promise.resolve(result).then(fromNativeLessValue) : fromNativeLessValue(result);
|
|
243
|
+
}
|
|
244
|
+
invokeContextualFunction(fn, args, ctx) {
|
|
245
|
+
const result = fn(args.map(toNativeLessValue), {
|
|
246
|
+
lookupVariable: (name) => {
|
|
247
|
+
const hit = ctx.lookupVariable(name);
|
|
248
|
+
return hit === null ? null : {
|
|
249
|
+
value: toNativeLessValue(hit.value),
|
|
250
|
+
important: hit.important
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
callFunction: (name, callArgs) => {
|
|
254
|
+
const answer = ctx.callFunction(name, callArgs.map(fromNativeLessValue));
|
|
255
|
+
return answer === void 0 ? void 0 : toNativeLessValue(answer);
|
|
256
|
+
},
|
|
257
|
+
currentFileInfo: ctx.currentFileInfo,
|
|
258
|
+
log: (record) => ctx.log(record),
|
|
259
|
+
markImportant: () => ctx.markImportant()
|
|
260
|
+
});
|
|
261
|
+
return isThenable(result) ? Promise.resolve(result).then(fromNativeLessValue) : fromNativeLessValue(result);
|
|
262
|
+
}
|
|
263
|
+
invokeRawFunction(fn, args, ctx) {
|
|
264
|
+
const contextual = this.#contextualFns.get(fn);
|
|
265
|
+
if (contextual) return this.invokeContextualFunction(contextual, args, ctx);
|
|
266
|
+
const native = this.#nativeFns.get(fn);
|
|
267
|
+
return native ? this.invokeNativeFunction(native, args) : void 0;
|
|
268
|
+
}
|
|
269
|
+
createPluginHost(optionsOrLoader) {
|
|
270
|
+
const loadPluginModule = typeof optionsOrLoader === "function" ? optionsOrLoader : optionsOrLoader?.loadPluginModule;
|
|
271
|
+
const host = {
|
|
272
|
+
...this.globalFns.length === 0 ? {} : { globalFns: this.globalFns },
|
|
273
|
+
invokeRawFunction: (fn, args, ctx) => this.invokeRawFunction(fn, args, ctx)
|
|
274
|
+
};
|
|
275
|
+
if (loadPluginModule) host.loadPlugin = (request) => {
|
|
276
|
+
const loaded = loadPluginModule(request);
|
|
277
|
+
const addLoaded = (module) => {
|
|
278
|
+
const functions = isLoadedPluginModule(module) ? module.functions : void 0;
|
|
279
|
+
if (!functions) return [];
|
|
280
|
+
return Object.entries(functions).map(([name, fn]) => this.addContextualFunction(name, fn));
|
|
281
|
+
};
|
|
282
|
+
return isThenable(loaded) ? Promise.resolve(loaded).then(addLoaded) : addLoaded(loaded);
|
|
283
|
+
};
|
|
284
|
+
return host;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/plugin.ts
|
|
289
|
+
/**
|
|
290
|
+
* Transitional package boundary for applications that previously installed the
|
|
291
|
+
* Less compatibility plugin. It contributes only native AST-v2 `Fn` values.
|
|
292
|
+
*
|
|
293
|
+
* Less 4 visitor hooks, `functionRegistry` callbacks, and `@plugin` script
|
|
294
|
+
* execution were legacy tree-runtime APIs. They are intentionally unsupported
|
|
295
|
+
* on the public AST-v2 compiler route: preserving them would require a second
|
|
296
|
+
* evaluator and Node-to-Less/ Less-to-Node conversion bridge.
|
|
297
|
+
*/
|
|
298
|
+
var LessCompatPlugin = class extends _jesscss_core.AbstractPlugin {
|
|
299
|
+
name = "less-compat";
|
|
300
|
+
bridges = /* @__PURE__ */ new WeakMap();
|
|
301
|
+
constructor(opts = {}) {
|
|
302
|
+
super();
|
|
303
|
+
this.opts = opts;
|
|
304
|
+
}
|
|
305
|
+
setContext(context) {
|
|
306
|
+
let bridge = this.bridges.get(context);
|
|
307
|
+
if (!bridge) {
|
|
308
|
+
bridge = new LessApiBridge(this.opts.plugins ?? []);
|
|
309
|
+
this.bridges.set(context, bridge);
|
|
310
|
+
}
|
|
311
|
+
const bridgeHost = bridge.createPluginHost(({ specifier, options }) => context.getPluginModule(specifier, options));
|
|
312
|
+
const functions = this.opts.functions;
|
|
313
|
+
const host = context.pluginHost;
|
|
314
|
+
const globalFns = [
|
|
315
|
+
...host?.globalFns ?? [],
|
|
316
|
+
...bridgeHost.globalFns ?? [],
|
|
317
|
+
...functions ?? []
|
|
318
|
+
];
|
|
319
|
+
context.pluginHost = {
|
|
320
|
+
...host,
|
|
321
|
+
...globalFns.length === 0 ? {} : { globalFns },
|
|
322
|
+
loadPlugin: bridgeHost.loadPlugin || host?.loadPlugin ? (request) => {
|
|
323
|
+
const baseLoaded = host?.loadPlugin?.(request);
|
|
324
|
+
const bridgeLoaded = bridgeHost.loadPlugin?.(request);
|
|
325
|
+
const merge = (baseFns, bridgeFns) => [...baseFns ?? [], ...bridgeFns ?? []];
|
|
326
|
+
const baseThenable = baseLoaded && typeof baseLoaded === "object" && "then" in baseLoaded;
|
|
327
|
+
const bridgeThenable = bridgeLoaded && typeof bridgeLoaded === "object" && "then" in bridgeLoaded;
|
|
328
|
+
if (baseThenable || bridgeThenable) return Promise.all([baseLoaded ?? [], bridgeLoaded ?? []]).then(([baseFns, bridgeFns]) => merge(baseFns, bridgeFns));
|
|
329
|
+
return merge(baseLoaded, bridgeLoaded);
|
|
330
|
+
} : void 0,
|
|
331
|
+
invokeRawFunction: (fn, args, ctx) => bridgeHost.invokeRawFunction?.(fn, args, ctx) ?? host?.invokeRawFunction?.(fn, args, ctx)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const lessCompatPlugin = (opts) => new LessCompatPlugin(opts);
|
|
336
|
+
//#endregion
|
|
337
|
+
exports.LessApiBridge = LessApiBridge;
|
|
338
|
+
exports.LessCompatPlugin = LessCompatPlugin;
|
|
339
|
+
exports.default = lessCompatPlugin;
|
|
340
|
+
exports.fromNativeLessValue = fromNativeLessValue;
|
|
341
|
+
exports.lessCompatPlugin = lessCompatPlugin;
|
|
342
|
+
exports.toNativeLessValue = toNativeLessValue;
|
package/lib/index.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Enables Less.js plugins and visitors to work with Jess AST.
|
|
6
6
|
*/
|
|
7
7
|
export { LessCompatPlugin, default as lessCompatPlugin, type LessCompatPluginOptions } from './plugin.js';
|
|
8
|
-
export
|
|
9
|
-
export {
|
|
8
|
+
export { LessApiBridge, fromNativeLessValue, toNativeLessValue, type ContextualPluginFunction, type NativeLessApi, type NativeLessFunction, type NativeLessFunctionRegistry, type NativeLessPlugin } from './less-api-bridge.js';
|
|
9
|
+
export { default } from './plugin.js';
|