Giter Site home page Giter Site logo

marko-js / marko Goto Github PK

View Code? Open in Web Editor NEW
13.1K 205.0 632.0 26.62 MB

A declarative, HTML-based language that makes building web apps fun

Home Page: https://markojs.com/

License: MIT License

JavaScript 61.82% HTML 11.43% Marko 8.90% TypeScript 17.86% CSS 0.01%
javascript ui-components dom vdom nodejs server-side-rendering client-side-rendering isomorphic frontend

marko's Introduction

Marko

A declarative, HTML-based language that makes building web apps fun 🔥

NPM Discord Chat Continuous Integration status Code coverage % # of monthly downloads OpenSSF Best Practices

DocsTry OnlineContributeGet Support

Intro

Marko is HTML reimagined as a language for building dynamic and reactive user interfaces. Almost any valid HTML is valid Marko, and Marko extends HTML for building modern applications more declaratively. Among these extensions are conditionals and lists, state, and components.

Marko supports both single-file components and components across separate files.

Single-file component

The following renders a button and a counter of how many times the button has been pressed:

click-count.marko

class {
  onCreate() {
    this.state = { count: 0 };
  }
  increment() {
    this.state.count++;
  }
}

style {
  .count {
    color: #09c;
    font-size: 3em;
  }
  .press-me {
    padding: 0.5em;
  }
}

<output.count>
  ${state.count}
</output>
<button.press-me on-click('increment')>
  Press me!
</button>

Multi-file component

The same component as above, but split into:

  • index.marko template file
  • component.js component JS logic file
  • style.css component styles file

index.marko

<output.count>
  ${state.count}
</output>
<button.press-me on-click('increment')>
  Press me!
</button>

component.js

export default {
  onCreate() {
    this.state = { count: 0 };
  },
  increment() {
    this.state.count++;
  },
};

style.css

.count {
  color: #09c;
  font-size: 3em;
}
.press-me {
  padding: 0.5em;
}

Concise Syntax

Marko also supports a beautifully concise syntax as an alternative to its HTML syntax:

Concise syntaxHTML syntax
ul.example-list
  for|color| of=[a, b, c]
    li -- ${color}
<ul class="example-list">
  <for|color| of=[a, b, c]>
    <li>${color}</li>
  </for>
</ul>

Getting Started

  1. npm install marko
  2. Read the docs

Community & Support

Stack Overflow Discord Twitter

Ask and answer StackOverflow questions with the #marko tag

Come hang out in our Discord chat, ask questions, and discuss project direction

Tweet to @MarkoDevTeam, or with the #markojs hashtag

Contributors

Marko would not be what it is without all those who have contributed ✨

All marko-js/marko GitHub contributors

Get Involved!

marko's People

Contributors

abiyasa avatar agliga avatar aselvaraj avatar austinkelleher avatar bhavinpatel04 avatar bkuri avatar brywatsonnn avatar ctdio avatar dependabot[bot] avatar dylanpiercey avatar github-actions[bot] avatar greenkeeper[bot] avatar hesulan avatar kristianmandrup avatar lokeshrishi avatar lulavalva avatar maberer avatar mlrawlings avatar newyork-anthonyng avatar oxala avatar patrick-steele-idem avatar philidem avatar ramahadevan avatar rturnq avatar scttdavs avatar seangates avatar sebring avatar tcrowe avatar tigt avatar yomed avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

marko's Issues

Avoid writing compiled template to disk on render

I really like the idea of html templating, but I discovered an issue when I tried to use it in my usual dev flow.

As I understand:
now the require-able templates are made possible through a temp file

fs.writeFileSync(tempFile, compiledSrc, {encoding: 'utf8'});
fs.renameSync(tempFile, targetFile);
return require(targetFile);

and although you can bust template cache with unload(templatePath), this doesn't clear node's module cache. So every time you require(targetFile) it will return the same compiled template regardless your meddling with marko's cache.
It's possible to clear node's cache with delete require.cache["absolutePath"] though, but manually it would be tedious to track every unneeded compiled template.

The problem this makes is during development when a file changes, I want to clear the cache so the engine can re-render the template, or refresh the browser for instance.

I think writing the compiled template to disk should be a feature, or an option, rather than a byproduct of rendering.

Is there a way not to write the compiled template directly to disk, but rather keep it only in memory?
I think it would be useful in development.

Thanks in advance!

Support $global in template.renderSync

This is not supported

template.renderSync({
  $global: {
    foo: "foo!"
   }
});

But this is (as seen in #16)

template.render{
  $global: {
    name: 'Frank'
  }
}, res);

The issue seems to be that $global processing is missing from the renderSync function.

https://github.com/raptorjs/marko/blob/master/runtime/marko-runtime.js#L65-L72

Template.prototype = {
   renderSync: function(data) {
       var out = new AsyncWriter();
       out.sync();
       this._(data, out);
       out.end();
       return out.getOutput();
}

But can be found in the render function.

https://github.com/raptorjs/marko/blob/master/runtime/marko-runtime.js#L84-L126

 render: function(data, out, callback) {
      ...
       var $global = data.$global;
       if ($global) {
           extend(out.global, $global);
       }

Add documentation for boolean attributes

Input data:

{
    options: [
        { 
            value: 'red',
            selected: false
        },
        { 
            value: 'green',
            selected: true
        },
        { 
            value: 'blue',
            selected: false
        }
    ]
}

Input Marko template:

<select>
    <option value="${option.value}" selected="${option.selected}" for="option in data.options">
        ${option.value}
    </option>
</select>

Output HTML:

<select>
    <option value="red">red</option>
    <option value="green" selected>green</option>
    <option value="blue">blue</option>
</select>

layout-use error

layout-use throws "SyntaxError: Unexpected token ILLEGAL"

module.exports = function create(__helpers) {
  var str = __helpers.s,
      empty = __helpers.e,
      notEmpty = __helpers.ne,
      ______layouts_default_layout_marko = __helpers.l(require.resolve("../../layouts/default-layout.marko")),
      node_modules_marko_node_modules_marko_layout_use_tag = require("node_modules\\marko\\node_modules\\marko-layout\\use-tag"),
      _tag = __helpers.t,
      node_modules_marko_node_modules_marko_layout_put_tag = require("node_modules\\marko\\node_modules\\marko-layout\\put-tag");

  return function render(data, out) {
    _tag(out,
      node_modules\marko\node_modules\marko_layout\use_tag,
      {
        "template": ______layouts_default_layout_marko,
        "*": {
          "showHeader": true
        }
      },
      function(_layout) {
        out.w(' ');
        _tag(out,
          node_modules\marko\node_modules\marko_layout\put_tag,
          {
            "into": "title",
            "layout": _layout
          },
          function() {
            out.w('My Page');
          });

        out.w(' ');
        _tag(out,
          node_modules\marko\node_modules\marko_layout\put_tag,
          {
            "into": "body",
            "layout": _layout
          },
          function() {
            out.w('BODY CONTENT');
          });

        out.w(' ');
      });

    out.w(' ');
  };
}

Programatically call taglibs

Is it possible to programmatically call taglibs?

Currently we can do the following when including template fragments:

<include template="src/modules/${module.alias}.marko"/>

I can include a different template based on the value of "module.alias".

I'd like to be able to have the same logic with taglibs. Something like this:

<var name="path" value="data.type" />
<app-test-${path} attr1="${data.attr1}" attr2="${data.attr2}" attr2="${data.attr2}"/>

Currently I can do the following:

<if test="data.type === "a">
    <app-test-a attr1="${data.attr1}" attr2="${data.attr2}" attr2="${data.attr2}"/>
</if>
<else-if test="data.type === "b">
    <app-test-b attr1="${data.attr1}" attr2="${data.attr2}" attr2="${data.attr2}"/>
</else-if>
<else>
    <app-test-c attr1="${data.attr1}" attr2="${data.attr2}" attr2="${data.attr2}"/>
</else>

This would work but I'd like to avoid having to copy over the same parameter code.

Introduce `render-tag-if` instead of `body-only-if`

Let's introduce a new tag attribute that will do the same thing as body-only-if.
Most of the time the intention is to skip the container and the focus is less in the body.

<a href="${data.linkUrl}" body-only-if="!data.linkUrl">
    Some body content
</a>

Can get changed to:

<a href="${data.linkUrl}" render-tag-if="data.linkUrl">
    Some body content
</a>

Provide way to declaratively map body content to a named input property

Instead of receiving the body content as a renderBody(out) function, it is sometimes helpful to have the body content be made available as a String property.

Proposal: Add support for a new "body-property": "<property_name>". For example, to map the body content (if any) to a "label" property:

{
    "attributes": {
        "label": "string"
    },
    "body-property": "label"
}

This allows the following:

<fancy-button label="My Label"/>

<fancy-button>
  My Label
</fancy-button>

In both cases, the label can be read in as input.label.

Add convenience method to create template-based renderer

Most renderers look similar to this:

var template = require('marko').load(require.resolve('./template.marko'));
module.exports = function(input, out) {
    template.render(input, out);
};

Add support for these convenient alternatives:

module.exports = require('marko').renderer(require.resolve('./template.marko'));
module.exports = require('marko').renderer(
    require.resolve('./template.marko'),
    function(input) {
        return {
            ..
        }
    });

NOTE: Lazily load the template that is provided and then store reference to loaded template.

Add documentation for "template-data" and "c-input" attributes

Example:

<some-component c-input="{ message: 'Hello World' }"/>

src/components/some-component/renderer.js:

module.exports = function render(input, out) {
   var message = input.message; // 'Hello World'
}

"template-data" is similar, but it only works for includes. Only one should be documented for consistency.

Introduce a core <preserve-whitespace> tag

The <preserve-whitespace> tag would produce no output, but it should tell the Marko compiler to not remove whitespace for nested nodes. This tag can be used an alternative to the c-whitespace="preserve" attribute when there is no convenient parent element to apply the attribute to. See discussion in Issue #10.

Support sending both c-input and attribute for custom tags

c-input works well for nested tags, but there are times when we need to pass down extra information from the parent tag, like this:

<tagname c-input="data.someObj" attribute="test"/>

Examples include when the parent contains the loop and we might need an index in the nested tag, or if parent tag-level logic affects a child tag.

This should also include support for passing a conditional, or at least a variable (that we can set with a conditional earlier in the file).

Thanks!

Self-nested taglibs

I started writing some code that I was really hoping would work but it doesn't seem to. I was wondering if self-nested taglibs would be possible because it would be extremely handy for my use case:

{
  "tags": {
    "mytag": {
      "renderer": "./mytag",
      "attributes": {
        "data": "string"
      },
      "body-function": "getChildren(__children)",
      "import-var": {
        "children": "__children"
      }
    }
  }
}
exports.render = function(input, out) {
  var children;
  if (input.getChildren) {
    children = [];
    input.getChildren({
      addChild: function(child) {
        children.push(child);
      }
    });
  }
  if (input.children)
    input.children.addChild(/* implementation detail */);
  else
    out.write(/* something utilizing children */);
};
<mytag data="a">
  <mytag data="b" />
  <mytag data="c" />
</mytag>

So basically my question is whether or not it is somehow possible to include both "body-function" and "import-var" on the same tag and self-nest them. When I try this I get an error that __children is undefined. Is there a way to achieve this currently?

assign is missing in the documentation

I was struggling with this issue for an hour until one of our coworkers told me how to do it.
Please add it to the documentation. ( to reassign a value to an existing variable we need to use assign)

<assign var="itemModel" value="items[itemIndex]" />

Marko exceptions not throwing the entire stack trace when

We are getting errors similar to what we see below, it will be great to get the entire stack trace here so we know what line has is the problem.

ERROR r1coltv/src/controllers/dealsHub: { '0': [TypeError: Cannot read property 'left' of undefined] } bad error

Dynamic component assignment through attribute

Hey,
This might be a hard thing to integrate, but I'd like to discuss it, maybe you will find it useful.
What want to accomplish is to load a component dynamically. Instead of using a html tag, I want to load it through a dedicated attributes value, lets say 'taglib'-attribute.

<div for="item in data.components" taglib="$!{item.component}-component" model="$!{item.value}">

or

<div for="item in data.components" taglib="item.component" model="$!{item.value}">

The problem is, if we'd like to be able to use every template available, we would need some sort of index with template names and their relative path. Alternatively, we could scope the available templates to only a certain directory, which would be easier, but not as clean.

This would require us to have a compiled template like this.

templates = { "component-one": "./components/component-one/template.marko" };
return function render(data, out) {
      out.w('xxx');
      forEach(data.components, function(item) {
       __helpers.l(require.resolve(templates[item.component])).render({"value": str(item.value)}, out);
      });
    out.w('xxx');
  };

or

templates = {
            "component-one": __helpers.l(require.resolve("./components/component-one/template.marko"))
        };
return function render(data, out) {
      out.w('xxx');
      forEach(data.components, function(item) {
          templates[item.component].render({"value": str(item.value)}, out);
      });
    out.w('xxx');
  };

Of course we could check, if array contains element etc. This is just a dirty example. I know that this adds a bit of an overhead to the lightweight templates, especially if you got lots of components, but I couldn't think of any different/better way of doing this without sacrificing client-side compatibility. What do you think?

I could integrate it, though it will take me some time to dive into the code. A few hints where I find the responsible bits would surely help me.

Marko and webpack

Hello,
is there a way to work with marko and webpack, a marko-loader perhaps ?
Thanks

Add documentation for the $global property

The $global property is used to add data that is available to all templates encountered during rendering by having the data hang off the wrapped writer.

template.render{
        $global: {
            name: 'Frank'
        }
    },
    res);

Given the following template:

<div>
    Hello ${out.global.name}!
</div>

The output would be the following:

<div>
    Hello Frank
</div>

Component library support

Usecase:

We would like to add a library of components. The path would be:
/node_modules/component-library/node_modules/custom-component/lib/

If the rendered template is somewhere else then this path won't be discovered.

Suggestion:

Add a way to add paths to the discovery feature

Respect whitespace

<my-first-tag>
<my-second-tag>

The new line between these two custom tags should be preserved.

Proposal: deprecate invokeBody(...) in favor of renderBody(out)

Currently, when using a custom tag that allows nested body content we compile the body content to a function that, when invoked, renders to the out from the closure. For example:

exports.create = function(__helpers) {
  var __renderer = __helpers.r,
      button_tag = __renderer(require("../button-tag")),
      __tag = __helpers.t;

  return function render(data, out) {
    __tag(out,
      button_tag,
      {
          disabled: false
      },
      function() {
        out.write('My Button Label');
      });
  };
};

This allows invokeBody() to be invoked without any arguments. However, this is problematic for a few reasons:

  • Rendering to a different out requires that the custom tag to be redeclared with an additional out variable that masks the out from the closure
  • Outside the scope of Marko template, it is problematic for a UI component renderer to be invoked while supplying nested body content as part of the input if the the UI component renderer does not call the input.invokeBody() method with the desired out.

To solve these problems, we are planning on making the following change:

  • Deprecating input.invokeBody(...) in favor of input.renderBody(out)
  • When rendering the body content by invoking the input.renderBody(out) method, the out must always be provided as the first argument. For example:
out.write('<button type="button">');
input.renderBody(out);
out.write('</button>');

The updated compiled template will be similar to the following:

exports.create = function(__helpers) {
  var __renderer = __helpers.r,
      button_tag = __renderer(require("../button-tag")),
      __tag = __helpers.t;

  return function render(data, out) {
    __tag(out,
      button_tag,
      {
          disabled: false
      },
      function(out) {
        out.write('My Button Label');
      });
  };
};

Finally, a new "body-function" property will be offered that allows the body content of a custom tag to be converted to a custom property on the input. For example:

{
    "body-function": "buildTabs(tabs)"
}

Sample compiled output:

exports.create = function(__helpers) {
  var __renderer = __helpers.r,
      tabs_tag = __renderer(require("../tabs-tag")),
      __tag = __helpers.t;

  return function render(data, out) {
    __tag(out,
      tabs_tag,
      {
          title: "My Tabs",
          buildTabs: function(tabsHelper) {
            ...
          }
      });
  };
};

Template compiler automatically closing html tags (breaking layout)

Hi,
I have a problem with components having their HTML tags closed, incorrectly, by the compiler. For example if I have the following components and html content (shown simplified):
(components/app-header/template.marko)
HTML content: <div class="wrapper"><div class="content">

(components/app-footer/template.marko)
HTML content: </div></div>

index.js
HTML content: <app-header></app-header><div>content here</div><app-footer></app-footer>

I can see the problem. The <app-header> component's compiled .js file has added the closing 'div' tags automatically, which breaks the layout as the 'wrapper' and 'content' elements are now closed, when they should be left open to contain the main page content.

Is there any setting to prevent this or a different way of laying out the pages?

Thanks,
Pat

Add support for specifying prefix when auto scanning for tags within directory

We should allow prefixes to be provided when scanning for tags within a directory.

For example, to scan single directory:

{
    "tags-dir": {
        "path": "./components",
        "prefix": "app-"
    }
}

To scan multiple directories:

{
    "tags-dir": [
        {
            "path": "./components",
            "prefix": "app-"
        },
        {
            "path": "./components/chat",
            "prefix": "chat-"
        }
    ]
}

Marko needs a new logo!

Let's create some logos for Marko!

I'll start things off with a really rough sketch of an idea:

marko-logo-small

Please feel free to take that idea and make it better or come up with something completely different.

All logos submitted here will be assumed to be licensed as public domain / CC0.

Contributors to the final logo will get credit on the main page and a custom printed T-Shirt with the logo on it!

htmlparser2 issue will not be resolved soon

Hi there,
i had an issue with adding following text in the marko template

hello world <> hello

after some debuging i found a problem within htmlparser2 module, and after submitting a pull request with fix to htmlparser2 module, author closed it and attached to other ticket ... at the end of the ticket he clearly says that he will not fix all the outstanding issues soon: "I have no idea when I'll have the time & be motivated to do it, so I can't give a timetable or anything" so maybe you should thing moving to other html parser because lacking of support.

IE conditionals support

I am trying to target IE browsers with some simple IE conditionals, but it seem to fail in marko template.

My template has these simple conditionals, but its not getting targeted properly. I don’t see the classes being added. (works however in plain html)

<!--[if lt IE 7]> <html class="lt-ie9 lt-ie8 lt-ie7" lang="${locale}"> <![endif]-->
<!--[if IE 7]> <html class="lt-ie9 lt-ie8" lang="${locale}"> <![endif]-->
<!--[if IE 8]> <html class="lt-ie9" lang="${locale}"> <![endif]-->
<!--[if !IE]> --><html lang="${locale}"><!--<![endif]—>

anything to do with the HTML comment’s?

Passing data to components

Hi,
Whats the best way to pass data to components. At present it appears you have to pass data from every template to the components e.g. in index.marko you would have
<app-header title="$data.title" env="$data.env" csrf="$data.csrf"></app-header>

I can then access the data from the component e.g.
<if test="data.env == 'development'">

If I try to access data.env without passing the data in from index.marko I get an error. Doing it this way if I want to pass new data into the app-header component in the future I would have to change ALL templates.
Is there a better way of doing this?

Thanks,
Pat (new to Marko and Node)

Please keep Changelog up to date

Especially with regards to breaking changes and feature deprecations.

It will help us make informed decisions about if/when to upgrade and what app-changes we need to make to stay up to date with the latest and greatest.

We have gotten ourselves into trouble a few times with mismatched versions between marko and supporting modules. I think having an accurate Changelog on each module would be hugely helpful in tracking down where we went awry.

Thanks!

Inconsistent implementation of custom tags

Any value under the value attribute are referenced to marko objects but the same is not true for custom tags.

<assign var="options" value = "util.getOptions(options, pattern)" />

Custom Tag only accepts $DATA

<ebayui-refit-itemgrid-pattern viewModel = $grid options = $options />

The below code will through an error in marko

<ebayui-refit-itemgrid-pattern viewModel = "grid" options = "options" />

Marko should promote one way of passing variables to support consistency.

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.