Programmer.
I like low-level programming.
Native template engine for Node.js
Home Page: https://eryn.exom.dev
License: MIT License
Consider this code:
[|# local.x = 5 |]
[| local |]
As expected, this is rendered as:
{"x":5}
Now, consider this code:
[|# local = { test: "stuff" } |]
[| local |]
This is rendered as:
{}
...instead of:
{"test":"stuff"}
The same happens with the context
object.
This is a limitation imposed by the bridge. The local
and context
objects are passed by reference to the bridge eval function. However, the references themselves are passed by value. This means that, while the objects can be changed, the references cannot.
Here's a workaround:
[|# Object.assign(local, { test: "stuff" }) |]
[| local |]
This produces the expected result. Keep in mind that if the local object already has some properties set, those will still persist.
The compiler doesn't localize the iterators inside any conditional/void templates.
It would be nice if users would can to pre/post process data on compile/render time.
The concept:
files --(content)--> hook --(modified content)--> run eryn as usual using the modified content
Suggested syntax:
eryn.setHook('preCompiler', (path, content) => {
// do whatever with file path or the content
return content;
});
When a file is rendered, the render
function segfaults randomly, because the output buffer is not expanded in some cases before memcpy
is used.
When there is a conditional template and the expression evaluates to false, the renderer jumps over the end template, resulting in the conditional stack not popping. This leads to very rare unexpected behavior that is very hard to predict. For example, in some cases, the renderer enters an else conditional template even if the previous conditional is true. This happens because the renderer retrieves information from another conditional template info structure, which was not popped and reports that the last conditional was false (thus resulting in the else conditional being rendered).
The problem is that the compiler emits the jump index after the end template, when it should emit it right before (such that the pop operation occurs).
If a self-closed component is present inside another component's content, the parent will not be rendered.
[|% comp.eryn |]
[|% comp2.eryn /|]
[|/|]
// Only comp2.eryn will be rendered.
This is an issue with the compiled OSH format, as the renderer cannot distinguish between a self-closed component and a component with content of length 0.
Possible fix: the compiler should write a componentEnd
marker to the the OSH output if the component is self-closed.
The user may expect template block passed to a component as content to have access to the context of the parent instead of the context passed to the component.
Current behavior:
// myscript.js
eryn.render('path/to/home.eryn', { data: 'some data' });
// home.eryn
{{ context.data }} <!-- it renders 'some data', as expected -->
{{comp comp.eryn with { title: 'Title', body: 'Body' \}}}
{{ context.title }} <!-- it renders 'Title' -->
{{ context.data }} <!-- it is undefined, so nothing will be rendered -->
{{endcomp}}
{{ context.data }} <!-- it renders 'some data', as expected as well -->
// comp.eryn
{{ content }}
Expected behavior:
// home.eryn
{{ context.data }} <!-- it renders 'some data', as expected -->
{{comp comp_path with { title: 'Title', body: 'Body' \}}}
{{ context.title }} <!-- it should be undefined -->
{{ context.data }} <!-- it should render 'some data' -->
{{endcomp}}
{{ context.data }} <!-- it renders 'some data', as expected as well -->
It would be a good idea to introduce an object like context
that is shared between all components. Here's an example:
eryn.render('file.eryn', { test: 'hello' }, { test2: 'This is shared' });
file.eryn
[| context.test |] // hello
[| shared.test2 |] // This is shared
[|% comp.eryn /|] // Note that no context is passed.
comp.eryn
[| context |] // { }
[| shared.test2 |] // This is shared
It should be nice to have a function, for example compileString(alias, template)
, that will compile the template passed as a string.
It would be nice to traverse an array in reverse items order. Introducing a new flag in the loop template, for example reversed
, would allow the user to iterate starting from the last item to the first one.
Suggested syntax example:
[|@ iterator : array reversed|]
body
[|end@|]
Comment templates should use the custom comment template end marker.
On CentOS, running npm i eryn
will fail because:
build-check
, because the command doesn't have parenthesesWorkaround/fix:
npm i eryn --ignore-scripts
cd node_modules/eryn
nano package.json
# Change the 'install' script to:
# (node build-check.js || npm run rebuild)
# i.e. put parentheses around the whole command
npm i # inside the eryn folder
The following code will throw an exception.
[| { test: "Test", dummy: "Dummy" } |]
This is because the template content starts with the character {
, which is treated as a block start. In order to force the evaluation function to treat it as an object expression, all scripts that start with {
should be surrounded by parentheses.
There is a downside to this. If the user wants to start the template contents with a block, he will have to write dummy code, such that the first character isn't {
, and the engine doesn't surround the content with parentheses.
[| /* BLOCK */ {
....
} |]
In conditional templates, the expression {}
is evaluated as false
instead of true
.
[|? {}|]
this will not be rendered
[|end?|]
This happens because the engine evaluates the expression using the eval
function, which produces the following results:
Boolean(eval("{}")) // False
Boolean(eval({})) // True
The problem only appears when the expression is directly written in the template, so it shouldn't be anything major.
You may be able to negate the boolean result returned by the expression in the conditional template.
Suggested syntax example:
[|! expression|] // execute the body if the expression is falsy
body
[|end!|]
It would be nice to have the ability to call an async function directly in the templates, by detecting await
keyword. Eryn would call the context function and wait for the Promise to be resolved.
[|? await context.connected() |]
Welcome back, [| await context.user() |]
[|:|]
Sign in to continue
[|end|]
Another, more simple solution (I think), would allow developers to store into local
variables values returned from async functions by using the new Async Template.
When attempting to render something like a boolean
, the renderer throws an exception (as expected). The exception states that the only supported return types are string
, number
, and others.
Along the supported types, ArrayBuffer
is also listed. However, it's not supported. What is supported, though, is Buffer
. The message should be fixed, as it could confuse the user.
How to reproduce:
<!-- home.eryn -->
[|% layout/index.eryn |]
[|% components/navbar.eryn \|] <!-- Should throw an error -->
[|end|]
<!-- layout/index.eryn -->
<doctype html>
<html>
...
[| content |]
</html>
<!-- components/navbar.eryn -->
<nav>
<a href="/">Home</a>
</nav>
The render
function segfaults when the output size is 0, resulting in a crash.
This can happen when the code only contains a conditional template that is false:
segfault.eryn
[|? false |]
stuff
[|end?|]
Since modules such as path
use different path separators for different platforms, it would be better to use /
everywhere. Therefore, every native function that works with external paths should first do the replacement, in order to avoid annoying cache misses.
It would be useful to have the ability to break the loop template.
A new template like [| break |]
should be added.
Very rarely, a segmentation fault occurs due to the fact that iterator localization overshoots the shift count when the input buffer is shifted before appending the localization suffix. The match index does not increase after the prefix has been applied, while the start pointer does. Hence, the shift count is bigger than it should be.
The reason this issue appears very rarely is that iterator names tend to be short, while the buffer expansion scheme provided by remem
uses powers of 2. Therefore, in most cases, the buffer is actually bigger and can fit the extra bytes. In very rare cases, however, the new size is very close to the needed size, and cannot fit any extra bytes.
An option, like throwOnCompileDirError
, that forces throwing errors on compile directory exception would be useful.
After rendering a component, any objects of type Buffer
get turned into their stringified counterparts. This is because the bridge uses stringify
to back-up the context
and local
objects.
Therefore, rendering a Buffer
object will result in a string.
When assigning to an iterator, or to a property of an iterator, those changes may take effect in the original array as well. In order to prevent this, an option should be added that specifies whether or not the iterator should be a copy of the original array element.
User would be able to set a custom path and would be able to use relative file paths on render functions.
Suggested syntax:
eryn.setOptions({
workingDirectory: 'my/path/to/views'
});
// eryn will use working directory to find the absolute path to the file passed
eryn.render('home.eryn', context);
// eryn should still be able to detect an absolute path to a file and ignore
// working directory in that case
eryn.render('C:\\projects\\meow\\cat.eryn', context);
The local
object is automatically saved and restored after each loop iteration. An option should be added that allows the user to change this behavior. By default, the automatic backups should not take place.
Loop template doesn't render arrays that contains only one item of any type.
{{for item in ['any string']}}
{{item}} <!-- Item is not rendered -->
{{endfor}}
It should be possible to traverse object keys with a for template.
Suggested syntax:
{{for key in object}}
{{key}}: {{object[key]}}
{{endfor}}
or
{{for pair in object}}
{{pair.key}}: {{pair.value}}
{{endfor}}
Since Windows supports both \
and /
as path separators, the path relativity detection algorithm must handle both cases.
Currently, it only correctly handles the case when /
is used. Therefore, this leads to problems on Windows.
When writing other templates in a comment template, the template ends must be escaped. This can become annoying, as you have to un-escape all ends if you decide to uncomment the templates in the future.
[|//
[| template here \|]
[|@ it : array \|]
|]
It would be better if comment templates had a special end marker, like this:
[|//
[| template here |]
[|@ it : array |]
//|]
In this case, the marker is //|]
, so the templateEnd
marker can be included in the comment without any issues.
Also, a proper option should be added for this marker.
Consider this code:
[|# local.x = {} |]
[|@ x.iterator : context.array |]
[|x.iterator|]
[|end|]
This does not work, because the compiler localizes the iterator like this:
local.x.iterator
However, the renderer treats x.iterator
as a whole property, like this:
local["x.iterator"]
Therefore, the local
object looks like this:
{ x: "{}, "x.iterator": ... }
...and local.x.iterator
is undefined, obviously.
The compiler should localize the iterator properly, such that this:
[| x.iterator |]
...becomes this:
[| local["x.iterator"] |]
The loop iterators are stored in global variables. Evidently, this is bad practice. It was a temporary solution which must now be rethought. It would be better to store them in a local
object, so the renderer doesn't affect the global object.
This means that the compiler needs a heuristic to determine if a token is an iterator (e.g. not a string or property of another object, not included in another token, etc). If so, it should prepend a prefix like local.
, which the renderer can use to refer to the local
object.
[|@ item : array |] // Stays 'item'
[|item|] // Changes to 'local.item'
[|"item"|] // Stays '"item"'
[|end@|]
Currently, a loop template only gives the user access to the item:
[|@ item : items |]
...
[| end |]
However, there are cases when the index is needed. Therefore, something like this should be added:
[|@ item, index : items |]
...
[| end |]
The index should be optional.
The compiler doesn't have enough space to store the loop end jump index in the output buffer. This results in segmentation fault, or occasional random values.
The buffer needs to be expanded before writing the loop end jump index.
Async functions for compiling and rendering content are a must. A dedicated worker thread should do the work, and the Node function itself should return a Promise. This is to prevent the event loop from being blocked.
Allocating a local variable like this:
[| local.x = 5 |]
[| local.x |]
...renders the value of the variable twice:
5
5
There should be a type of template that doesn't render the result of an expression, such that variable allocations execute silently:
[|# local.x = 5 |]
[| local.x |]
...will be rendered as:
5
The local
object is backed up before entering a loop, and restored after the end of the loop. Therefore, any changes done to the local
object inside the loop should not propagate.
[|# local.x = 5 |]
[|@ iterator : array |]
[|# local.x = 1 |]
[| end |]
The local
object remains unchanged after the loop. However, not in this case:
[|# local.x = { value: 10 } |]
[|@ iterator : array |]
[|# local.x.value = 5 |]
[| end |]
In this case, local.x.value
becomes 5, due to the fact that the backup is done using shallow copy in order to favor performance.
An option should be added, such that deep cloning is used instead. Of course, this may lead to a significant performance drop, but it's still good to have such an option available in case it is needed.
There should be an option to enable support for comments in the plaintext.
Text <!-- this is a multiline
comment-->
{{ it should not work in templates <!--}} -->
They should be disabled by default. The options could be called commentStart
and commentEnd
.
When looping over an array of strings, the iterator takes the values as Object
types instead of string
types.
This:
[|# local.test = ["test0", "test1"] |]
[|@ it : local.test |]
[|it|]
[|end@|]
...will be rendered as:
"test0"
"test1"
...instead of:
test0
test1
This happens because it
is actually an Object
, and the renderer stringifies it, thus rendering the pair of quotes which shouldn't be rendered.
Currently, each template type has its own end marker. Here's an example:
[|? condition |]
body
[|end?|]
[|@ it : array |]
body
[|end@|]
It would be nice to add an universal end that works on all template types, like this:
[|? condition |]
body
[|end|]
[|@ it : array |]
body
[|end|]
In the future, this may replace all other ends, such that the syntax becomes cleaner.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.