Coder Social home page Coder Social logo

unexomwid / eryn Goto Github PK

View Code? Open in Web Editor NEW
8.0 2.0 0.0 8.17 MB

Native template engine for Node.js

Home Page: https://eryn.exom.dev

License: MIT License

CMake 0.84% JavaScript 5.99% C++ 89.06% C 4.10%
template-engine node-module node-js native javascript cxx cmake server-side-rendering n-api

eryn's Introduction

About

Programmer.

I like low-level programming.

eryn's People

Contributors

pebly avatar unexomwid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

eryn's Issues

Local and context objects cannot have their references modified

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.

Add hooks

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;
});

Random SIGSEGV when rendering a file

When a file is rendered, the render function segfaults randomly, because the output buffer is not expanded in some cases before memcpy is used.

Conditional stack is not popped for simple conditionals: rare unexpected behavior with conditional templates

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).

Self-closed component inside content does not render parent

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.

Use context of the parent on [| content |] block

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 -->

Add shared context between components

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

Iterate for loop template in reverse order

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@|]

Eryn fails to build on CentOS

On CentOS, running npm i eryn will fail because:

  • there are no prebuilds for CentOS
  • the installation script only runs build-check, because the command doesn't have parentheses

Workaround/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

Object expressions inside templates are treated as blocks

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 */ {
    ....
} |]

The expression '{ }' is evaluated as false instead of true

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.

Native support for inverted conditional templates

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!|]

Support for async function calls inside Eryn's templates

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.

Note: The current solution for this problem is creating an async wrapper in Node.js, and render eryn files with resolved data from Promises.

Fix template return type error message

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.

Render segmentation fault when output size is 0

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?|]

Replace '\' with '/' regardless of the platform

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.

Very rare SIGSEGV at compile-time and render-time caused by iterator localization

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.

Loop iterator assignment behavior is unpredictable

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.

Option to set a working directory

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);

Iterate for loop template through object properties

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}}

Path relativity is not correctly determined on Windows

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.

Add custom end marker for comment templates

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.

Iterators containing '.' are not localized properly

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"] |]

Localize loop iterators

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@|]

Add optional index for arrays in loop templates

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.

Add templates that don't render the result

Example

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

Add bridge option which makes deep clones of objects instead of shallow clones

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.

Add support for comments with customizable syntax

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.

Loop template converts string iterator to Object

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.

Add support for template universal end

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.