
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Xyfy is a tiny framework designed to let you use JSX in your codebase.
/* @jsx jsx @jsxFrag fragment */
import xyfy, { fragment } from './xyfy';
// Create a JSX adapter for basic elements
const jsx = xyfy({
root({ children }) {
// Return arbitrary values
return {
type: 'root',
children,
};
},
heading({ level = 1, children }) {
/* ... */
},
pagaraph({ children }) {
/* ... */
},
/* ... */
});
// Functional components work too!
function Section({ title, level, children }) {
return (
<>
<heading level={level}>{title}</heading>
<paragraph>{children}</paragraph>
</>
);
}
saveAST(
<root>
<Section title="Installation">
<code lang="shell">$ npm install --save xyfy</code>
🔮✨
</Section>
</root>,
);
$ npm install --save xyfy
🔮✨
A functional component is just a function with a particular structure: it takes a props
parameter and returns 'something'. The props
passed to a functional component is guaranteed to at least be an object, never null
or undefined
.
(props) => any
A special feature of props
is the children
prop. children
represents nodes that belong to the functional component. children
may be any value, depending on what was passed to the JSX adapter.
jsx(component, props, ...children)
component
: A functional component.props
: null
or an object containing this element's props
(excluding children
).children
: Values to place into the children
prop and pass to the component
.jsx
is the basic form of a JSX adapter that handles combining props
and children
and passing them to a functional component
.
import { jsx } from 'xyfy';
function PrintProps(props) {
console.log(props);
}
jsx(PrintProps, null);
// <PrintProps />
// => {}
jsx(PrintProps, {
items: ['apples', 'flour', 'butter'],
});
// <PrintProps items={['apples', 'flour', 'butter']} />
// => { items: ['apples', 'flour', 'butter'] }
jsx(PrintProps, { title: 'Installation' }, 'npm install --save xyfy');
// <PrintProps title="Installation">
// npm install --save xyfy
// </printProps>
//
// => {
// title: 'Installation',
// children: 'npm install --save xyfy'
// }
jsx(PrintProps, { title: 'API' }, 'Functional Components', 'jsx', 'xyfy');
// => {
// title: 'API',
// children: ['Functional Components', 'jsx', 'xyfy']
// }
xyfy(intrinsics)
intrinsics
: An object where each key is a camelCase string1 and the value is a functional component for a basic element.Returns a function similar to jsx
, except the first parameter is called elem
: a key of intrinsics
(indicating which basic element to create) or a functional component.
xyfy
augments the basic jsx
adapter by teaching it how to handle strings defined by the intrinsics
map you pass to it. If you pass an object like { applePie(props) { ... } }
to xyfy
then the jsx
function it returns knows what to do when it's given a string element, e.g. jsx('applePie', null)
(<applePie />
).
1 In theory, this could also be a symbol, but it's unknown why you'd want to do this. The camelCase restriction is a byproduct of how a JSX interpreter differentiates basic elements and components.
import xyfy from 'xyfy';
const jsx = xyfy({
basic() {
console.log('basic');
},
});
function Component() {
console.log('Component');
}
jsx('basic', null);
// <basic />
// => basic
jsx(Component, null);
// <Component />
// => Component
The recommended setup for using Xyfy is to create a file in your project that exports your basic element adapter. Then, import that adapter as jsx
in your files that use JSX.
Show, don't tell.
Your directory structure should look something like this:
├─ node_modules/
│ ├─ xyfy/
│ └─ ...
├─ src/
│ ├─ jsx.js <-- Our JSX adapter
│ └─ index.jsx <-- A file that uses JSX
└─ package.json
Then jsx.js
would contain the definition of your basic elements:
// jsx.js
import xyfy from 'xyfy';
// This makes it convenient to use fragments with only one import
export { fragment } from 'xyfy';
// Export your JSX adapter
export default xyfy({
root({ children }) {
return {
type: 'root',
children,
};
},
heading({ level = 1, children }) {
/* ... */
},
pagaraph({ children }) {
/* ... */
},
/* ... */
});
And any file using your JSX adapter would import that adapter:
// index.jsx
/* @jsx jsx @jsxFrag fragment */
import jsx, { fragment } from './jsx';
function Section({ title, level, children }) {
return (
<>
<heading level={level}>{title}</heading>
<paragraph>{children}</paragraph>
</>
);
}
saveAST(
<root>
<Section title="Installation">
<code lang="shell">$ npm install --save xyfy</code>
🔮✨
</Section>
</root>,
);
🎉 Ta-da!
(Don't forget to transpile with Babel!)
An alternative setup is to use the jsx
adapter provided by Xyfy and simply declare your basic elements as exported functions:
├─ node_modules/
│ ├─ xyfy/
│ └─ ...
├─ src/
│ ├─ elements/ <-- Basic elements
│ │ ├─ Root.js
│ │ ├─ Heading.js
│ │ ├─ Paragraph.js
│ │ └─ ...
│ ├─ components/
│ │ └─ Section.jsx <-- A component glues together elements
│ └─ index.jsx
└─ package.json
Under this structure, your code would be far more modular:
// elements/root.js
export default function Root({ children }) {
return {
type: 'root',
children,
};
}
// index.jsx
/* @jsx jsx @jsxFrag fragment */
import jsx, { fragment } from './jsx';
import Section from './components/Section';
import Root from './elements/Root';
import Code from './elements/Code';
saveAST(
<Root>
<Section title="Installation">
<Code lang="shell">$ npm install --save xyfy</Code>
🔮✨
</Section>
</Root>,
);
You'll notice under this setup that basic elements now have to be declared in PascalCase (with a capital letter at the front). This is due to the fact that the default jsx
adapter has no basic elements. That means everything passed to it acts like a functional component.
Take a peek at these sizes:
Kind | Size |
---|---|
Source1 | 2,269 B |
Built1,2 | 2,386 B |
Minified3 | 930 B |
Minified & Gzipped | 443 B |
(Yes, that's in bytes!)
Last Updated: 15 Aug 2019
1 Sizes with this mark include tons of comments and whitespace. They're meant to show how much space this package takes up in your node_modules
directory, rather than in a properly bundled application.
2 Passed through Babel to ensure compatibility with Node 8. This includes conversion to CommonJS, which adds some bloat.
3 Minified using terser, a fork of UglifyJS that supports ES2015+.
Both are functions that take some props
and return 'something'.
A target structure (the thing your JSX models) is comprised of some basic building blocks, similar to how the universe is comprised of atoms or how a toy house is comprised of legos. These individual bits are called basic elements (sometimes also called intrinsic1 elements).
Sometimes it's helpful to recognize a pattern of basic elements that isn't quite the full product. When you assign a name to a subgroup of basic elements it becomes a component. A component can be comprised of basic elements, other components, or some combination of both!
For example, if your target structure is a programming language AST:
abc
), an expression (123
), and a declaration (x = y
), among others.abc = 123
), which takes an identifier
and expression
and returns a declaration
.1 The term intrinsic refers to the idea that the element is natural or essential.
children
work?The children
prop has some special semantics associated with it. The value you get for your children
prop will depend on what ends up being passed to the jsx
adapter. There's a few scenarios:
Case | Value of children |
---|---|
No children are passedjsx(elem, props) | undefined . |
One child is passedjsx(elem, props, child) | the value of child ; if child is 'peach' then props will have children: 'peach' . |
Multiple children are passedjsx(elem, props, child1, child2, ..., childN) | an array containing the children passed; [child1, child2, ..., childN] . |
Arrays also follow the rules above.
If you pass a single array as a child to an element, it will appear to that functional component that multiple children were passed.
jsx(elem, props, [abc, 'easy as', 123]);
// => children: [abc, 'easy as', 123]
If you pass an array alongside other children, then that array will be inside the children
array passed to the functional component.
jsx(elem, props, 'Guess the song:', [abc, 'easy as', 123]);
// => children: ['Guess the song:', [abc, 'easy as', 123]]
Xyfy is meant to be just the glue between JSX and some data represented by that JSX. On the other hand, React is a framework for building user interfaces that react to events (e.g. element interaction). Xyfy and React don't take up the same problem space, so comparing them isn't very useful. 😕
To make it plain:
The core of JSX is that it's just a different way of representing a pattern of calling functions. To illustrate:
<foo>
<bar>baz</bar>
</foo>;
// is equivalent to:
jsx('foo', null, jsx('bar', null, 'baz'));
The latter can become cumbersome and difficult to read when the primary goal of your code is to create some nested structure, e.g. an AST:
/**
* A trimmed-down excerpt from an actual code sample.
*/
function documentClassLikeFields(fields) {
return fields.length === 0
? undefined
: table(
['center', 'center', null],
[
tableRow(
filterUndef([
tableCell(text('Name')),
tableCell(text('Type')),
propsHaveDesc ? tableCell(text('Description')) : undefined,
]),
),
...fields.map(field =>
tableRow(
filterUndef([
// Name
tableCell(text(field.name)),
// Type
tableCell(
formatExcerptReferences(ctx, field.propertyTypeExcerpt),
),
// Description
propsHaveDesc
? tableCell(convertDocSection(field.description))
: undefined,
]),
),
),
],
);
}
The nesting starts to fight against you, it becomes difficult to determine what's a regular function and what generates the structure, and the presence of magic parameters (parameters that don't have inherent meaning, e.g. ['center', 'center', null]
) all hinder understanding and readability of the code.
JSX is positioned to solve this by:
But after transpiling, it's just a bunch of functions again. Anything you can do with JSX can be identically created with functions.
Thus the decision to adopt JSX into a project comes down to aesthetics: does your code read better as JSX or as functions?
FAQs
Barebones JSX framework
The npm package xyfy receives a total of 3 weekly downloads. As such, xyfy popularity was classified as not popular.
We found that xyfy demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.