diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fee3b7af..7f7af3cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,188 @@ Changelog # 2.x -## 2.0.x +## 2.4.x + +### 2.4.0 + +- Added support for short-hand tags and attributes + +Old `marko-taglib.json`: + +```json +{ + "tags": { + "my-hello": { + "renderer": "./hello-renderer", + "attributes": { + "name": "string" + } + } + } +} +``` + +Short-hand `marko-taglib.json`: + +```json +{ + "": { + "renderer": "./hello-renderer", + "@name": "string" + } +} +``` + +- Fixes #61 Simplify parent/child relationships + +Marko now supports custom tags in the following format: `` + +Example usage: + +```html + + + Content for Home + + + Content for Profile + + + Content for Messages + + +``` + +___ui-tabs/marko-tag.json___ + +```json +{ + "@orientation": "string", + "@tabs []": { + "@title": "string" + } +} +``` + +___ui-tabs/renderer.js___ + +```javascript +var template = require('marko').load(require.resolve('./template.marko')); + +exports.renderer = function(input, out) { + var tabs = input.tabs; + + // Tabs will be in the following form: + // [ + // { + // title: 'Home', + // renderBody: function(out) { ... } + // }, + // { + // title: 'Profile', + // renderBody: function(out) { ... } + // }, + // { + // title: 'Messages', + // renderBody: function(out) { ... } + // } + // ] + console.log(tabs.length); // Output: 3 + + template.render({ + tabs: tabs + }, out); + +}; +``` + +___ui-tabs/template.marko___ + +```html +
+ +
+
+ +
+
+
+``` + +## 2.3.x + +### 2.3.2 + +Fixes #66 - Allow circular dependencies when loading templates + +### 2.3.1 + +- Testing framework changes +- Fixes #65 - Generated variable name is an empty string in some cases + +### 2.3.0 + +- Fixes #53 Merge c-input with attr props + +## 2.2.x + +### 2.2.2 + +Fixes #60 Don't replace special operators for body functions + +### 2.2.1 + +- Fixes #58 Added support for MARKO_CLEAN env variable (force recompile of all loaded templates). Example usage: + +```bash +MARKO_CLEAN=true node run.js +``` + +- Code formatting: add spaces in var code + +### 2.2.0 + +- Fixes #51 Allow body content to be mapped to a String input property +- Fixes #52 Remove JavaScript comments from JSON taglib files before parsing + +## 2.1.x + +### 2.1.6 + +- Fixes #50 Initialize the loader after the runtime is fully initialized + +### 2.1.5 + +- Fixes #50 Ensure that all instances of marko have hot-reload and browser-refresh enabled + +### 2.1.4 + +- Allowing complex var names (i.e. LHS) for the `` tag. + +### 2.1.3 + +- Minor change: Slight improvement to code to resolve tag handler + +### 2.1.2 + +- Minor change: Improve how renderer is resolved + +### 2.1.1 + +- Fixes #48 name in marko-tag.json should override default name given during discovery ### 2.1.0 - Fixes #47 - Added support for "taglib-imports" +## 2.0.x + ### 2.0.12 - Fixes #31 - Add support for providing prefix when scanning for tags diff --git a/README.md b/README.md index 4eec741d0..610ff65cd 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,15 @@ Marko is an extensible, streaming, asynchronous, [high performance](https://gith Marko is a perfect match for Node.js since it supports writing directly to an output stream so that HTML can be sent over the wire sooner. Marko automatically flushes around asynchronous fragments so that the HTML is delivered in the optimized number of chunks. Because Marko is an asynchronous templating language, additional data can be asynchronously fetched even after rendering has begun. These characteristics make Marko an excellent choice for creating high performance websites. +For building rich UI components with client-side behavior please check out the companion [marko-widgets](https://github.com/raptorjs/marko-widgets) taglib. + __[Try Marko Online!](http://raptorjs.org/marko/try-online/)__ ![Marko Syntax](syntax.png) Syntax highlighting available for [Atom](https://atom.io/) by installing the [language-marko](https://atom.io/packages/language-marko) package. -![eBay Open Source](https://raw.githubusercontent.com/raptorjs/optimizer/master/images/ebay.png) +![eBay Open Source](https://raw.githubusercontent.com/lasso-js/lasso/master/images/ebay.png) @@ -24,56 +26,62 @@ Syntax highlighting available for [Atom](https://atom.io/) by installing the [la - [Another Templating Language?](#another-templating-language) - [Design Philosophy](#design-philosophy) - [Usage](#usage) - - [Template Rendering](#template-rendering) - - [Callback API](#callback-api) - - [Streaming API](#streaming-api) - - [Synchronous API](#synchronous-api) - - [Asynchronous Rendering API](#asynchronous-rendering-api) - - [Browser-side Rendering](#browser-side-rendering) - - [Using the RaptorJS Optimizer](#using-the-raptorjs-optimizer) - - [Using Browserify](#using-browserify) - - [Template Compilation](#template-compilation) - - [Sample Compiled Template](#sample-compiled-template) + - [Template Rendering](#template-rendering) + - [Callback API](#callback-api) + - [Streaming API](#streaming-api) + - [Synchronous API](#synchronous-api) + - [Asynchronous Rendering API](#asynchronous-rendering-api) + - [Browser-side Rendering](#browser-side-rendering) + - [Using Lasso.js](#using-lassojs) + - [Using Browserify](#using-browserify) + - [Template Compilation](#template-compilation) + - [Sample Compiled Template](#sample-compiled-template) - [Language Guide](#language-guide) - - [Template Directives Overview](#template-directives-overview) - - [Text Replacement](#text-replacement) - - [Expressions](#expressions) - - [Includes](#includes) - - [Variables](#variables) - - [Conditionals](#conditionals) - - [if...else-if...else](#ifelse-ifelse) - - [Shorthand Conditionals](#shorthand-conditionals) - - [Conditional Attributes](#conditional-attributes) - - [Looping](#looping) - - [for](#for) - - [Loop Status Variable](#loop-status-variable) - - [Loop Separator](#loop-separator) - - [Range Looping](#range-looping) - - [Property Looping](#property-looping) - - [Custom Iterator](#custom-iterator) - - [Macros](#macros) - - [def](#def) - - [invoke](#invoke) - - [Structure Manipulation](#structure-manipulation) - - [attrs](#attrs) - - [body-only-if](#body-only-if) - - [Comments](#comments) - - [Whitespace](#whitespace) - - [Helpers](#helpers) - [Global Properties](#global-properties) - - [Custom Tags and Attributes](#custom-tags-and-attributes) - - [Async Taglib](#async-taglib) - - [Layout Taglib](#layout-taglib) + - [Template Directives Overview](#template-directives-overview) + - [Text Replacement](#text-replacement) + - [Expressions](#expressions) + - [Includes](#includes) + - [Variables](#variables) + - [Conditionals](#conditionals) + - [if...else-if...else](#ifelse-ifelse) + - [Shorthand Conditionals](#shorthand-conditionals) + - [Conditional Attributes](#conditional-attributes) + - [Looping](#looping) + - [for](#for) + - [Loop Status Variable](#loop-status-variable) + - [Loop Separator](#loop-separator) + - [Range Looping](#range-looping) + - [Property Looping](#property-looping) + - [Custom Iterator](#custom-iterator) + - [Macros](#macros) + - [def](#def) + - [invoke](#invoke) + - [Structure Manipulation](#structure-manipulation) + - [attrs](#attrs) + - [body-only-if](#body-only-if) + - [Comments](#comments) + - [Whitespace](#whitespace) + - [Helpers](#helpers) + - [Global Properties](#global-properties) + - [Custom Tags and Attributes](#custom-tags-and-attributes) + - [Async Taglib](#async-taglib) + - [Layout Taglib](#layout-taglib) - [Custom Taglibs](#custom-taglibs) - - [Tag Renderer](#tag-renderer) - - [marko-taglib.json](#marko-taglibjson) - - [Sample Taglib](#sample-taglib) - - [Defining Tags](#defining-tags) - - [Defining Attributes](#defining-attributes) - - [Scanning for Tags](#scanning-for-tags) - - [Nested Tags](#nested-tags) - - [Taglib Discovery](#taglib-discovery) + - [Tag Renderer](#tag-renderer) + - [marko-taglib.json](#marko-taglibjson) + - [Sample Taglib](#sample-taglib) + - [Defining Tags](#defining-tags) + - [Defining Attributes](#defining-attributes) + - [Scanning for Tags](#scanning-for-tags) + - [Nested Tags](#nested-tags) + - [Taglib Discovery](#taglib-discovery) - [FAQ](#faq) +- [Additional Resources](#additional-resources) + - [Further Reading](#further-reading) + - [Screencasts](#screencasts) + - [Demo Apps](#demo-apps) + - [Tools](#tools) +- [Changelog](#changelog) - [Discuss](#discuss) - [Contributors](#contributors) - [Contribute](#contribute) @@ -406,18 +414,18 @@ templatePath.render({ }); ``` -You can then bundle up the above program for running in the browser using either [optimizer](https://github.com/raptorjs/optimizer) (recommended) or [browserify](https://github.com/substack/node-browserify). +You can then bundle up the above program for running in the browser using either [Lasso.js](https://github.com/lasso-js/lasso) (recommended) or [browserify](https://github.com/substack/node-browserify). -### Using the RaptorJS Optimizer +### Using Lasso.js -The `optimizer` CLI can be used to generate resource bundles that includes all application modules and all referenced Marko template files using a command similar to the following: +The `lasso` CLI can be used to generate resource bundles that includes all application modules and all referenced Marko template files using a command similar to the following: ```bash -# First install the optimizer and the optimizer-marko plugin -npm install optimizer --global -npm install optimizer-marko +# First install the lasso and the lasso-marko plugin +npm install lasso --global +npm install lasso-marko -optimizer --main run.js --name my-page --plugins optimizer-marko +lasso --main run.js --name my-page --plugins lasso-marko ``` This will produce a JSON file named `build/my-page.html.json` that contains the HTML markup that should be used to include the required JavaScript and CSS resources that resulted from the page optimization. @@ -425,7 +433,7 @@ This will produce a JSON file named `build/my-page.html.json` that contains the Alternatively, you can inject the HTML markup into a static HTML file using the following command: ```bash -optimizer --main run.js --name my-page --plugins optimizer-marko --inject-into my-page.html +lasso --main run.js --name my-page --plugins lasso-marko --inject-into my-page.html ``` @@ -444,7 +452,7 @@ browserify -t markoify run.js > browser.js ## Template Compilation -The Marko compiler produces a Node.js-compatible, CommonJS module as output. This output format has the advantage that compiled template modules can benefit from a context-aware module loader and templates can easily be transported to work in the browser using the [RaptorJS Optimizer](https://github.com/raptorjs/optimizer) or [Browserify](https://github.com/substack/node-browserify). +The Marko compiler produces a Node.js-compatible, CommonJS module as output. This output format has the advantage that compiled template modules can benefit from a context-aware module loader and templates can easily be transported to work in the browser using [Lasso.js](https://github.com/lasso-js/lasso) or [Browserify](https://github.com/substack/node-browserify). The `marko` module will automatically compile templates loaded by your application on the server, but you can also choose to precompile all templates. This can be helpful as a build or test step to catch errors early. @@ -1414,45 +1422,57 @@ A tag renderer should be mapped to a custom tag by creating a `marko-taglib.json } ``` -### Defining Tags - -Tags can be defined by adding a `"tags"` property to your `marko-taglib.json`: +Marko also supports a short-hand for declaring tags and attributes. The following `marko-taglib.json` is equivalent to the `marko-taglib.json` above: ```json { - "tags": { - "my-hello": { - "renderer": "./hello-renderer", - "attributes": { - "name": "string" - } - }, - "my-foo": { - "renderer": "./foo-renderer", - "attributes": { - "*": "string" - } - } + "": { + "renderer": "./hello-renderer", + "@name": "string" } } ``` -Every tag should be associated with a renderer. When a custom tag is used in a template, the renderer will be invoked at render time to produce the HTML/output. +The short-hand will be used for the remaining of this documentation. -#### Defining Attributes +## Defining Tags -If you provide attributes then the Marko compiler will do validation to make sure only the supported attributes are provided. A wildcard attribute (`"*"`) allows any attribute to be passed in. Below are sample attribute definitions: +Tags can be defined by adding `"": ` properties to your `marko-taglib.json`: -_Multiple attributes:_ -```javascript -"attributes": { - "message": "string", // String - "my-data": "expression", // JavaScript expression - "*": "string" // Everything else will be added to a special "*" property +```json +{ + "": { + "renderer": "./hello-renderer", + "@name": "string" + }, + "": { + "renderer": "./foo-renderer", + "@*": "string" + }, + "": "./path/to/my-bar/marko-tag.json", + "": { + "template": "./baz-template.marko" + }, } ``` -### Scanning for Tags +Every tag should be associated with a renderer or a template. When a custom tag is used in a template, the renderer (or template) will be invoked at render time to produce the HTML/output. If a `String` path to a `marko-tag.json` for a custom tag then the target `marko-tag.json` is loaded to define the tag. + +## Defining Attributes + +If you provide attributes then the Marko compiler will do validation to make sure only the supported attributes are provided. A wildcard attribute (`"@*"`) allows any attribute to be passed in. Below are sample attribute definitions: + +_Multiple attributes:_ + +```javascript +{ + "@message": "string", // String + "@my-data": "expression", // JavaScript expression + "@*": "string" // Everything else will be added to a special "*" property +} +``` + +## Scanning for Tags Marko supports a directory scanner to make it easier to maintain a taglib by introducing a few conventions: @@ -1494,9 +1514,7 @@ _In `renderer.js`:_ ```javascript exports.tag = { - "attributes": { - "name": "string" - } + "@name": "string" } ``` @@ -1504,116 +1522,180 @@ _In `marko-tag.json`:_ ```javascript { - "attributes": { - "name": "string" - } + "@name": "string" } ``` _NOTE: It is not necessary to declare the `renderer` since the scanner will automatically use `renderer.js` as the renderer._ -### Nested Tags +## Nested Tags It is often necessary for tags to have a parent/child or ancestor/descendent relationship. For example: ```html - - + + Content for Home - - + + Content for Profile - - + + Content for Messages - + ``` -Marko supports this by leveraging JavaScript closures in the compiled output. A tag can introduce scoped variables that are available to nested tags. This is shown in the sample `marko-taglib.json` below: +Nested tags can be declared in the parent tag's `marko-tag.json` as shown below: + +___ui-tabs/marko-tag.json___ ```json { - "tags": { - "ui-tabs": { - "renderer": "./tabs-tag", - "body-function": "getTabs(__tabsHelper)" - }, - "ui-tab": { - "renderer": "./tab-tag", - "import-var": { - "tabs": "__tabsHelper" - }, - "attributes": { - "title": "string" - } - } + "@orientation": "string", + "@tabs []": { + "@title": "string" } } ``` -In the above example, the `` tag will introduce a scoped variable named `tabs` that is then automatically imported by the nested `` tags. When the nested `` tags render they can use the scoped variable to communicate with the renderer for the `` tag. +This allows a `tabs` to be provided using nested `` tags or the tabs can be provided as a `tabs` attribute (e.g. `` tags will be made available to the renderer as part of the `tabs` property for the parent ``. Because of the `[]` suffix on `[]` the tabs property will be of type `Array` and not a single object. That is, the `[]` suffix is used to declare that a nested tag can be repeated. The sample renderer that accesses the nested tabs is shown below: -The complete code for this example is shown below: - -_components/tabs/renderer.js:_ +___ui-tabs/renderer.js___ ```javascript -var templatePath = require.resolve('./template.marko'); -var template = require('marko').load(templatePath); +var template = require('marko').load(require.resolve('./template.marko')); -exports.render = function(input, out) { - var nestedTabs; +exports.renderer = function(input, out) { + var tabs = input.tabs; - if (input.getTabs) { - nestedTabs = []; - // Invoke the body function to discover nested tags - input.getTabs({ // Invoke the body with the scoped "tabs" variable - addTab: function(tab) { - tab.id = tab.id || ("tab" + nestedTabs.length); - nestedTabs.push(tab); - } - }); - } else { - nestedTabs = input.tabs || []; - } + // Tabs will be in the following form: + // [ + // { + // title: 'Home', + // renderBody: function(out) { ... } + // }, + // { + // title: 'Profile', + // renderBody: function(out) { ... } + // }, + // { + // title: 'Messages', + // renderBody: function(out) { ... } + // } + // ] + console.log(tabs.length); // Output: 3 - - // Now render the markup for the tabs: template.render({ - tabs: nestedTabs + tabs: tabs }, out); + }; ``` -_components/tab/renderer.js:_ +Finally, the template to render the `` component will be similar to the following: -```javascript -exports.render = function(input, out) { - // Register with parent but don't render anything - input.tabs.addTab(input); -}; -``` - -_components/tabs/template.marko:_ +___ui-tabs/template.marko___ ```html
-
+
``` +Below is an example of using nested tags that are not repeated: + +```html + + + Header content + + + + Body content + + + + Footer content + + +``` + +The `marko-tag.json` for the `` tag will be similar to the following: + +___ui-overlay/marko-tag.json___ + +```json +{ + "@header
": { + "@class": "string" + }, + "@body ": { + "@class": "string" + }, + "@footer