Format + lint (#1016)

* add prettierignore

* switch to eslint:recommended + eslint-config-prettier

* fix eslint violations

* remove more .jshintrc files

* better conditional structure

* add prettier and update prettier ignore

* add precommit hook to run prettier

* add lint check to precommit and format check to ci

* format all the things

* add generated files

* let npm do it's thing with package.json
This commit is contained in:
Michael Rawlings 2018-03-09 10:02:11 -08:00 committed by Dylan Piercey
parent ee17aa53ab
commit 0f5e639775
2252 changed files with 22108 additions and 18102 deletions

17
.eslintignore Normal file
View File

@ -0,0 +1,17 @@
# minified
*.min.js
# test
*actual*
*expected*
input.js
# generated
*dist*
*generated*
*.marko.js
*.marko.*.js
*.html.js
~vdom.skip
node_modules

14
.eslintrc Normal file
View File

@ -0,0 +1,14 @@
{
"extends": [
"eslint:recommended",
"prettier"
],
"env": {
"node": true,
"es6": true
},
"globals": {
"document": true,
"ShadowRoot": true
}
}

View File

@ -3,20 +3,20 @@
This project adheres to the [eBay Code of Conduct](http://ebay.github.io/codeofconduct).
By participating in this project you agree to abide by its terms.
- Be friendly and patient.
* Be friendly and patient.
- Be welcoming: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
* Be welcoming: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
- Be considerate: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that were a world-wide community, so you might not be communicating in someone elses primary language.
* Be considerate: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that were a world-wide community, so you might not be communicating in someone elses primary language.
- Be respectful: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. Its important to remember that a community where people feel uncomfortable or threatened is not a productive one.
* Be respectful: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. Its important to remember that a community where people feel uncomfortable or threatened is not a productive one.
- Be careful in the words that we choose: we are a community of professionals, and we conduct ourselves professionally.
* Be careful in the words that we choose: we are a community of professionals, and we conduct ourselves professionally.
- Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior arent acceptable.
* Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior arent acceptable.
- Try to understand why we disagree: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively.
* Try to understand why we disagree: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively.
- Remember that were different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesnt mean that theyre wrong. Dont forget that it is human to err and blaming each other doesnt get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
* Remember that were different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesnt mean that theyre wrong. Dont forget that it is human to err and blaming each other doesnt get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
Please visit http://ebay.github.io/codeofconduct for the full code of conduct.

View File

@ -10,9 +10,10 @@
## I just have a question
Before you ask, check our [existing questions](https://github.com/marko-js/marko/issues?page=2&q=is%3Aissue+label%3Atype%3Aquestion&utf8=%E2%9C%93) to see if your question has already been answered. If not, go ahead an open an issue or join us in [gitter](https://gitter.im/marko-js/marko) to ask a question.
Before you ask, check our [existing questions](https://github.com/marko-js/marko/issues?page=2&q=is%3Aissue+label%3Atype%3Aquestion&utf8=%E2%9C%93) to see if your question has already been answered. If not, go ahead an open an issue or join us in [gitter](https://gitter.im/marko-js/marko) to ask a question.
Please be sure to use [markdown code blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) when posting code on GitHub or Gitter:
````
```marko
<div>some marko ${code}</div>
@ -29,25 +30,27 @@ Not sure if that typo is worth a pull request? Found a bug and know how to fix i
We are always thrilled to receive pull requests. We do our best to process them quickly. If your pull request is not accepted on the first try, don't get discouraged! We'll work with you to come to an acceptable solution.
Prior to merging your PR, you will need to sign the [JS Foundation CLA](https://cla.js.foundation/marko-js/marko). It's pretty straight-forward and only takes a minute. You can even sign it now if you're thinking about contributing.
Prior to merging your PR, you will need to sign the [JS Foundation CLA](https://cla.js.foundation/marko-js/marko). It's pretty straight-forward and only takes a minute. You can even sign it now if you're thinking about contributing.
> **TIP:** If you're new to GitHub or open source you can check out this [free course](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) on how to contribute to an open source project.
### Running tests
Before submitting your PR, make sure that all new and previous tests pass and that [coverage](https://coveralls.io/github/marko-js/marko?branch=master) has not decreased:
```
npm run test-coverage
```
While developing you can run a single test group and use [grep](https://mochajs.org/#-g---grep-pattern) to filter the tests:
```
npm run mocha -- --grep=lifecycle
```
### Adding tests
Marko makes use of directory based test suites. Take a look at the `render` test suite:
Marko makes use of directory based test suites. Take a look at the `render` test suite:
<pre>
<a href="../test/">test/</a>
@ -66,15 +69,16 @@ Marko makes use of directory based test suites. Take a look at the `render` tes
The `html.test.js` file will run and read all the directories under `render/fixtures` and for each directory (`attrs`, `for-tag`, etc.) it will run `test.js`, render `template.marko` and assert that it is equivalent to the content of `expected.html`.
To add a new test, you'll find the appropriate test suite, copy a fixture, and modify it to add the new test.
#### Skipping a test
A few of the tests suites use the same fixtures for multiple test scenarios. For example, the `component-browser` tests run once rendering the component in a browser environment and a second time rendering in a server environment, then hydrating in the browser.
A few of the tests suites use the same fixtures for multiple test scenarios. For example, the `component-browser` tests run once rendering the component in a browser environment and a second time rendering in a server environment, then hydrating in the browser.
For some tests, it might be necessary to skip the test in one of these scenarios. This is done by exporting a `skipHydrate` (or similiarly named) property from the fixture. The value of the property should be a string explaining why the test is skipped.
For some tests, it might be necessary to skip the test in one of these scenarios. This is done by exporting a `skipHydrate` (or similiarly named) property from the fixture. The value of the property should be a string explaining why the test is skipped.
#### Adding a failing test case
If you've discovered an issue and are able to reproduce it, but don't have a fix, consider submitting a PR with a failing test case. You can mark a fixture as expected to fail by appending `.fails` to the directory name:
If you've discovered an issue and are able to reproduce it, but don't have a fix, consider submitting a PR with a failing test case. You can mark a fixture as expected to fail by appending `.fails` to the directory name:
```
⤷ fixtures/
@ -87,7 +91,8 @@ Expected failures won't cause [Travis CI](https://travis-ci.org/marko-js/marko)
### Debugging tests
If you need to dig a bit deeper into a failing test, use the `--inspect-brk` flag, open Chrome DevTools, and click on the green nodejs icon (<img height="16" src="https://user-images.githubusercontent.com/1958812/37050480-d53e4276-2128-11e8-8c7a-f5d842956c98.png"/>) to start debugging. Learn more about [debugging node](https://www.youtube.com/watch?v=Xb_0awoShR8&t=103s) from this video.
If you need to dig a bit deeper into a failing test, use the `--inspect-brk` flag, open Chrome DevTools, and click on the green nodejs icon (<img height="16" src="https://user-images.githubusercontent.com/1958812/37050480-d53e4276-2128-11e8-8c7a-f5d842956c98.png"/>) to start debugging. Learn more about [debugging node](https://www.youtube.com/watch?v=Xb_0awoShR8&t=103s) from this video.
```
npm run mocha -- --grep=test-name --inspect-brk
```
@ -101,9 +106,9 @@ $ debugger;
### Updating snapshots
A number of the test suites make use snapshot comparisons. For example, the `render` tests compare the rendered html against a stored snapshot. Similarly, the `compiler` tests compare the generated JavaScript module againt a stored snapshot. Any changes compared to the snapshot should be looked at closely, but there are some cases where it is fine that the output has changed and the snapshot needs to be updated.
A number of the test suites make use snapshot comparisons. For example, the `render` tests compare the rendered html against a stored snapshot. Similarly, the `compiler` tests compare the generated JavaScript module againt a stored snapshot. Any changes compared to the snapshot should be looked at closely, but there are some cases where it is fine that the output has changed and the snapshot needs to be updated.
To update a snapshot, you can copy the contents from the `actual` file to the `expected` file in the fixture directory. You can also use the `UPDATE_EXPECTATIONS` env variable to cause the test runner to update the `expected` file for all currently failing tests in a suite:
To update a snapshot, you can copy the contents from the `actual` file to the `expected` file in the fixture directory. You can also use the `UPDATE_EXPECTATIONS` env variable to cause the test runner to update the `expected` file for all currently failing tests in a suite:
```
UPDATE_EXPECTATIONS=1 npm run mocha
@ -115,17 +120,17 @@ Comment on the issue and let us know you'd like to tackle it. If for some reason
Here's some to get started with:
- [good first issue](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22): great for new contributors
- [help wanted](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issues: won't be tackled in the near future by the maintainers... we need your help!
- [unassigned](https://github.com/marko-js/marko/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20no%3Aassignee%20) issues: open issues that no one has claimed... yet
* [good first issue](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22): great for new contributors
* [help wanted](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issues: won't be tackled in the near future by the maintainers... we need your help!
* [unassigned](https://github.com/marko-js/marko/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20no%3Aassignee%20) issues: open issues that no one has claimed... yet
## Reporting bugs and other issues
A great way to contribute to the project is to send a detailed report when you encounter an issue. Even better: submit a PR with a failing test case ([see how](#adding-a-failing-test-case)).
A great way to contribute to the project is to send a detailed report when you encounter an issue. Even better: submit a PR with a failing test case ([see how](#adding-a-failing-test-case)).
Check that [our issue database](https://github.com/marko-js/marko/issues) doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates. Rather than leaving a "+1" or "I have this too" comment, you can add a [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) to let us know that this is also affecting you without cluttering the conversation. However, if you have ways to reproduce the issue or have additional information that may help resolving the issue, please leave a comment.
Check that [our issue database](https://github.com/marko-js/marko/issues) doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates. Rather than leaving a "+1" or "I have this too" comment, you can add a [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) to let us know that this is also affecting you without cluttering the conversation. However, if you have ways to reproduce the issue or have additional information that may help resolving the issue, please leave a comment.
We have an [ISSUE_TEMPLATE](ISSUE_TEMPLATE.md) that will populate your textarea when you go to open an issue. Use the relevant section and remove the rest.
We have an [ISSUE_TEMPLATE](ISSUE_TEMPLATE.md) that will populate your textarea when you go to open an issue. Use the relevant section and remove the rest.
Please provide as much detail as possible.
@ -140,6 +145,7 @@ Security reports are greatly appreciated and we will publicly thank you for it.
Once you post an issue, a maintainer will add one or more labels to it. Below is a guideline for the maintainers and anyone else who is interested in what the various labels mean.
### Type
![](https://img.shields.io/badge/type-bug-dd0000.svg)
![](https://img.shields.io/badge/type-unverified%20bug-aa3300.svg)
![](https://img.shields.io/badge/type-feature-0099dd.svg)
@ -150,15 +156,16 @@ Once you post an issue, a maintainer will add one or more labels to it. Below is
Every issue should be assigned one of these.
- **bug**: A bug report
- **unverified-bug**: A bug report that has not been verified
- **feature**: A feature request
- **question**: A question about how to do something in Marko
- **community**: Related to community building, improving the contribution process, etc.
- **tech debt**: Related to refactoring code, test structure, etc.
- **docs**: Related to documentation/website
* **bug**: A bug report
* **unverified-bug**: A bug report that has not been verified
* **feature**: A feature request
* **question**: A question about how to do something in Marko
* **community**: Related to community building, improving the contribution process, etc.
* **tech debt**: Related to refactoring code, test structure, etc.
* **docs**: Related to documentation/website
### Scope
![](https://img.shields.io/badge/scope-parser-5500cc.svg)
![](https://img.shields.io/badge/scope-compiler-cc0077.svg)
![](https://img.shields.io/badge/scope-runtime-eebb00.svg)
@ -168,47 +175,51 @@ Every issue should be assigned one of these.
What part of the Marko stack does this issue apply to? In most cases there should only be one of these.
- **parser**: Relates to [`htmljs-parser`](https://github.com/marko-js/htmljs-parser)
- **compiler**: Relates to the [compiler](../src/compiler) (server only)
- **runtime**: Relates to the [runtime](../src/runtime) (isomorphic/universal)
- **core-taglib**: Relates to [custom tags](../src/taglib) that ship with Marko
- **components**: Relates to [components](../src/components)
- **tools**: Relates to editor plugins, commandline tools, etc.
* **parser**: Relates to [`htmljs-parser`](https://github.com/marko-js/htmljs-parser)
* **compiler**: Relates to the [compiler](../src/compiler) (server only)
* **runtime**: Relates to the [runtime](../src/runtime) (isomorphic/universal)
* **core-taglib**: Relates to [custom tags](../src/taglib) that ship with Marko
* **components**: Relates to [components](../src/components)
* **tools**: Relates to editor plugins, commandline tools, etc.
### Status
![](https://img.shields.io/badge/status-backlog-223344.svg)
![](https://img.shields.io/badge/status-in%20progress-006b75.svg)
![](https://img.shields.io/badge/status-needs%20review-0e8a16.svg)
In many cases, additional *actions* should be taken when applying one of these.
In many cases, additional _actions_ should be taken when applying one of these.
- **backlog**: Tasks planned to be worked on
- **in progress**: This is currently being worked on.
- **needs review**: This issue needs to be followed up on.
* **backlog**: Tasks planned to be worked on
* **in progress**: This is currently being worked on.
* **needs review**: This issue needs to be followed up on.
### Reason closed
![](https://img.shields.io/badge/reason%20closed-resolved-99cc99.svg)
![](https://img.shields.io/badge/reason%20closed-duplicate-cc99cc.svg)
![](https://img.shields.io/badge/reason%20closed-declined-bb6666.svg)
![](https://img.shields.io/badge/reason%20closed-not%20a%20bug-997744.svg)
![](https://img.shields.io/badge/reason%20closed-inactivity-bfd4f2.svg)
![](https://img.shields.io/badge/reason%20closed-no%20issue-c5def5.svg)
- **resolved**: The question was answered, the bug was fixed, or the feature was implemented.
- **duplicate**: Someone has already posted the same or a very similar issue. A comment should be added that references the original issue.
- **declined**: This feature will not be implemented.
- **not a bug**: This is not a bug, but either user error or intended behavior.
- **inactivity**: There was not enough info to reproduce the bug or not enough interest in the feature to hash out an implementation plan and the conversation has stalled.
- **no issue**: This wasn't so much an issue as a comment
* **resolved**: The question was answered, the bug was fixed, or the feature was implemented.
* **duplicate**: Someone has already posted the same or a very similar issue. A comment should be added that references the original issue.
* **declined**: This feature will not be implemented.
* **not a bug**: This is not a bug, but either user error or intended behavior.
* **inactivity**: There was not enough info to reproduce the bug or not enough interest in the feature to hash out an implementation plan and the conversation has stalled.
* **no issue**: This wasn't so much an issue as a comment
### Other
![](https://img.shields.io/badge/-good%20first%20issue-00cccc.svg)
![](https://img.shields.io/badge/-help%20wanted-33cc88.svg)
![](https://img.shields.io/badge/-blocked-6b0c0c.svg)
![](https://img.shields.io/badge/-needs%20more%20info-dd9944.svg)
![](https://img.shields.io/badge/-user%20land-e8c9c9.svg)
- **good first issue**: Small tasks that would be good for first time contributors.
- **help wanted**: Not on the roadmap, but we'd love for someone in the community to tackle it.
- **blocked**: Cannot be completed until something else happens first. This should be described in a comment with a link to the blocking issue.
- **needs more info**: The original poster needs to provide more information before action can be taken.
- **user land**: Something that probably won't be added to core, but could be implemented/proved out as a separate module.
* **good first issue**: Small tasks that would be good for first time contributors.
* **help wanted**: Not on the roadmap, but we'd love for someone in the community to tackle it.
* **blocked**: Cannot be completed until something else happens first. This should be described in a comment with a link to the blocking issue.
* **needs more info**: The original poster needs to provide more information before action can be taken.
* **user land**: Something that probably won't be added to core, but could be implemented/proved out as a separate module.

View File

@ -1,40 +1,52 @@
<!-----------------------------------------------------------------------
| IF BUG REPORT (skip to next section for feature suggestion) |
----------------------------------------------------------------------->
## Bug Report
### Marko Version: x.x.x
<!--- Provide the exact version of marko in which you see the bug. You can run `npm ls marko` to see this. -->
### Details
<!--- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug. How has this bug affected you? What were you trying to accomplish? -->
### Expected Behavior
<!--- Tell us what should happen -->
### Actual Behavior
<!--- Tell us what happens instead -->
### Possible Fix
<!--- Not obligatory, but suggest a fix or reason for the bug -->
<details><summary>Additional Info</summary>
### Your Environment
<!-- Include as many relevant details about the environment you experienced the bug in -->
* Environment name and version (e.g. Chrome 39, node.js 5.4):
* Operating System and version (desktop or mobile):
* Link to your project:
### Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug include code to reproduce, if relevant -->
1.
2.
3.
4.
1. first...
2.
3.
4.
### Stack Trace
<!-- If an error is thrown, provide the stack trace here -->
</details>
@ -42,18 +54,25 @@
<!--------------------------------
| IF FEATURE SUGGESTION |
-------------------------------->
## New Feature
### Description
<!--- Provide a detailed description of the change or addition you are proposing -->
### Why
<!--- Why is this change important to you? How would you use it? -->
<!--- How can it benefit other users? -->
### Possible Implementation & Open Questions
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
<!--- What still needs to be discussed -->
### Is this something you're interested in working on?
<!--- Yes or no -->

View File

@ -1,21 +1,27 @@
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
## Screenshots (if appropriate):
## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] I have updated/added documentation affected by my changes.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
* [ ] My code follows the code style of this project.
* [ ] I have updated/added documentation affected by my changes.
* [ ] I have read the **CONTRIBUTING** document.
* [ ] I have added tests to cover my changes.
* [ ] All new and existing tests passed.
_Disclaimer: Contributions via GitHub pull requests are gladly accepted from their original author. Along with any pull requests, please state that the contribution is your original work and that you license the work to the project under the project's open source license. Whether or not you state this explicitly, by submitting any copyrighted material via pull request, email, or other means you agree to license the material under the project's open source license and warrant that you have the legal authority to do so._

View File

@ -1,2 +0,0 @@
/src/taglibs/async/client-reorder-runtime.js
/src/taglibs/async/client-reorder-runtime.min.js

View File

@ -1,33 +0,0 @@
{
"predef": [
"document",
"ShadowRoot"
],
"node" : true,
"esnext": true,
"boss" : false,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": true,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": false,
"white": false,
"eqeqeq": false,
"latedef": "func",
"unused": "vars",
"strict": false,
"eqnull": true
}

22
.prettierignore Normal file
View File

@ -0,0 +1,22 @@
# minfied
*.min.js
# tests
*actual*
*expected*
input.*
# generated
/dist/
/test-dist/
/test-generated/
*.marko.js
*.html.js
*.xml.js
*.generated.js
.nyc_output
coverage
# controlled by npm's formatter
package-lock.json
package.json

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,12 @@ Learn more on [markojs.com](http://markojs.com/), and even [Try Marko Online!](h
# Get Involved
- **Contributing**: Pull requests are welcome!
- Read [`CONTRIBUTING.md`](.github/CONTRIBUTING.md) and check out our [bite-sized](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3Adifficulty%3Abite-sized) and [help-wanted](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3Astatus%3Ahelp-wanted) issues
- Submit github issues for any feature enhancements, bugs or documentation problems
- **Support**: Join our [gitter chat](https://gitter.im/marko-js/marko) to ask questions to get support from the maintainers and other Marko developers
- Questions/comments can also be posted as [github issues](https://github.com/marko-js/marko/issues)
- **Discuss**: Tweet using the `#MarkoJS` hashtag and follow [@MarkoDevTeam](https://twitter.com/MarkoDevTeam)
* **Contributing**: Pull requests are welcome!
* Read [`CONTRIBUTING.md`](.github/CONTRIBUTING.md) and check out our [bite-sized](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3Adifficulty%3Abite-sized) and [help-wanted](https://github.com/marko-js/marko/issues?q=is%3Aissue+is%3Aopen+label%3Astatus%3Ahelp-wanted) issues
* Submit github issues for any feature enhancements, bugs or documentation problems
* **Support**: Join our [gitter chat](https://gitter.im/marko-js/marko) to ask questions to get support from the maintainers and other Marko developers
* Questions/comments can also be posted as [github issues](https://github.com/marko-js/marko/issues)
* **Discuss**: Tweet using the `#MarkoJS` hashtag and follow [@MarkoDevTeam](https://twitter.com/MarkoDevTeam)
# Installation
@ -39,7 +39,8 @@ the Marko.js website.
The following single-file component renders a button and a counter with the
number of times the button has been clicked. [Try this example now!](http://markojs.com/try-online/?file=%2Fcomponents%2Fcomponents%2Fclick-count%2Findex.marko)
__click-count.marko__
**click-count.marko**
```marko
class {
onCreate() {
@ -75,7 +76,8 @@ The same component as above split into an `index.marko` template file,
`component.js` containing your component logic, and `style.css` containing your
component style:
__index.marko__
**index.marko**
```marko
<div.count>
${state.count}
@ -85,27 +87,29 @@ __index.marko__
</button>
```
__component.js__
**component.js**
```js
module.exports = {
onCreate() {
this.state = { count:0 };
},
increment() {
this.state.count++;
}
onCreate() {
this.state = { count: 0 };
},
increment() {
this.state.count++;
}
};
```
__style.css__
**style.css**
```css
.count {
color:#09c;
font-size:3em;
color: #09c;
font-size: 3em;
}
.example-button {
font-size:1em;
padding:0.5em;
font-size: 1em;
padding: 0.5em;
}
```

8
benchmark/.eslintrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"rules": {
"no-console": 0
},
"parserOptions": {
"sourceType": "module"
}
}

View File

@ -1,40 +0,0 @@
{
"predef": [
],
"globals": {
"define": true,
"require": true
},
"node" : true,
"esnext" : true,
"browser" : true,
"boss" : false,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": true,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": false,
"white": false,
"eqeqeq": false,
"latedef": false,
"unused": "vars",
"jquery": true,
"strict": false,
"eqnull": true
}

View File

@ -1,20 +1,21 @@
var nodePath = require('path');
var nodePath = require("path");
var Module = require('module').Module;
var Module = require("module").Module;
var oldResolveFilename = Module._resolveFilename;
var rootDir = nodePath.join(__dirname, '../');
var rootDir = nodePath.join(__dirname, "../");
Module._resolveFilename = function(request, parent, isMain) {
if (request.charAt(0) !== '.') {
var firstSlash = request.indexOf('/');
var targetPackageName = firstSlash === -1 ? request : request.substring(0, firstSlash);
if (request.charAt(0) !== ".") {
var firstSlash = request.indexOf("/");
var targetPackageName =
firstSlash === -1 ? request : request.substring(0, firstSlash);
if (targetPackageName === 'marko') {
request = request.substring('marko'.length);
request = rootDir + request;
}
}
if (targetPackageName === "marko") {
request = request.substring("marko".length);
request = rootDir + request;
}
}
return oldResolveFilename.call(this, request, parent, isMain);
};
};

View File

@ -1,37 +1,32 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import babelPlugin from 'rollup-plugin-babel';
import envify from 'envify';
import path from 'path';
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import babelPlugin from "rollup-plugin-babel";
import envify from "envify";
import path from "path";
process.env.NODE_ENV = 'production';
process.env.NODE_ENV = "production";
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.jsx'),
format: 'iife',
moduleName: 'app',
entry: path.join(__dirname, "client.jsx"),
format: "iife",
moduleName: "app",
plugins: [
babelPlugin({
include: [],
babelrc: false,
"presets": [
["es2015", { "loose": true, "modules": false }],
"stage-0"
],
"plugins": ["inferno"]
presets: [["es2015", { loose: true, modules: false }], "stage-0"],
plugins: ["inferno"]
}),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.jsx' ]
extensions: [".js", ".jsx"]
})
],
dest: path.join(__dirname, '../build/bundles/inferno.js')
dest: path.join(__dirname, "../build/bundles/inferno.js")
};

View File

@ -1,6 +1,7 @@
var app = require('./components/app');
app.renderSync({
name: 'Frank',
colors: ['red', 'green', 'blue']
var app = require("./components/app");
app
.renderSync({
name: "Frank",
colors: ["red", "green", "blue"]
})
.appendTo(document.body);
.appendTo(document.body);

View File

@ -1,3 +1,3 @@
var helpers = require('marko/src/runtime/vdom/helpers');
var helpers = require("marko/src/runtime/vdom/helpers");
console.log('HELPERS:', helpers);
console.log("HELPERS:", helpers);

View File

@ -1,35 +1,34 @@
process.env.NODE_ENV = 'production';
process.env.NODE_ENV = "production";
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import markoify from 'markoify';
import envify from 'envify';
import minpropsify from 'minprops/browserify';
import path from 'path';
import commonjsPlugin from "rollup-plugin-commonjs";
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import markoify from "markoify";
import envify from "envify";
import minpropsify from "minprops/browserify";
import path from "path";
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.js'),
format: 'iife',
moduleName: 'app',
entry: path.join(__dirname, "client.js"),
format: "iife",
moduleName: "app",
plugins: [
browserifyPlugin(markoify),
browserifyPlugin(envify),
browserifyPlugin(minpropsify),
nodeResolvePlugin({
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.marko' ]
extensions: [".js", ".marko"]
}),
commonjsPlugin({
include: [],
extensions: [ '.js', '.marko' ]
extensions: [".js", ".marko"]
})
],
dest: path.join(__dirname, '../build/bundles/marko.js')
dest: path.join(__dirname, "../build/bundles/marko.js")
};

View File

@ -1,28 +1,30 @@
console.log('Minifying JavaScript bundles...');
console.log("Minifying JavaScript bundles...");
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
const UglifyJS = require("uglify-js");
const formatNumber = require('format-number')();
const formatNumber = require("format-number")();
var buildDir = path.join(__dirname, 'build');
var bundlesDir = path.join(__dirname, 'build/bundles');
var bundlesMinDir = path.join(__dirname, 'build/bundles.min');
var buildDir = path.join(__dirname, "build");
var bundlesDir = path.join(__dirname, "build/bundles");
var bundlesMinDir = path.join(__dirname, "build/bundles.min");
try {
fs.mkdirSync(bundlesMinDir);
} catch(e) {}
} catch (e) {
/* ignore error */
}
var promiseChain = Promise.resolve();
function getVersion(name) {
return require(name + '/package.json').version;
return require(name + "/package.json").version;
}
function leftPad(str, padding) {
if (str.length < padding) {
str = new Array(padding - str.length).join(' ') + str;
str = new Array(padding - str.length).join(" ") + str;
}
return str;
@ -30,10 +32,10 @@ function leftPad(str, padding) {
var minifiers = {
gcc: function minifyGCC(src, file) {
const gcc = require('google-closure-compiler-js');
const gcc = require("google-closure-compiler-js");
const options = {
jsCode: [{src: src}],
languageIn: 'ES5'
jsCode: [{ src: src }],
languageIn: "ES5"
};
const out = gcc.compile(options);
@ -49,7 +51,7 @@ var minifiers = {
return UglifyJS.minify(src, {
fromString: true
}).code;
} catch(e) {
} catch (e) {
if (e.line != null) {
console.error(`Failed to minify ${file}`);
console.error(` Location: ${file}:${e.line}:${e.col}`);
@ -58,7 +60,6 @@ var minifiers = {
}
throw e;
}
},
both: function(src, file) {
var withGCC = minifiers.gcc(src, file);
@ -75,14 +76,14 @@ var sizes = {};
var targetLib = process.argv[2];
bundleFiles.forEach((filename) => {
if (!filename.endsWith('.js')) {
bundleFiles.forEach(filename => {
if (!filename.endsWith(".js")) {
return;
}
var file = path.join(bundlesDir, filename);
var ext = path.extname(filename);
var lib = filename.slice(0, 0-ext.length);
var lib = filename.slice(0, 0 - ext.length);
if (targetLib && lib !== targetLib) {
return;
@ -90,16 +91,16 @@ bundleFiles.forEach((filename) => {
console.log(`Minifying ${file}...`);
var src = fs.readFileSync(file, { encoding: 'utf8' });
var src = fs.readFileSync(file, { encoding: "utf8" });
var minifiedSrc = minifier(src, file);
console.log(`Done minifying ${file}`);
var minFile = path.join(bundlesMinDir, filename);
fs.writeFileSync(minFile, minifiedSrc, { encoding: 'utf8' });
fs.writeFileSync(minFile, minifiedSrc, { encoding: "utf8" });
var sizeInfo = sizes[lib] = {};
var sizeInfo = (sizes[lib] = {});
promiseChain = promiseChain.then(() => {
return new Promise((resolve, reject) => {
@ -110,16 +111,21 @@ bundleFiles.forEach((filename) => {
}
// Compare the sizes
var minifiedBuffer = new Buffer(minifiedSrc, 'utf8');
var minifiedBuffer = new Buffer(minifiedSrc, "utf8");
// console.log(nodePath.basename(templateInfo.outputCompileMinifiedFile) + ': ' + gzippedBuffer.length + ' bytes gzipped (' + minifiedBuffer.length + ' bytes uncompressed)');
sizeInfo.gzipped = gzippedBuffer.length;
sizeInfo.min = minifiedBuffer.length;
var libVersion = getVersion(lib);
var sizeFilename = lib + (libVersion ? '-' + libVersion : '') + '.json';
var sizeFilename =
lib + (libVersion ? "-" + libVersion : "") + ".json";
fs.writeFileSync(path.join(buildDir, sizeFilename), JSON.stringify(sizeInfo, null, 4), { encoding: 'utf8' });
fs.writeFileSync(
path.join(buildDir, sizeFilename),
JSON.stringify(sizeInfo, null, 4),
{ encoding: "utf8" }
);
resolve();
});
@ -132,11 +138,15 @@ promiseChain.then(() => {
for (var lib in sizes) {
var sizeInfo = sizes[lib];
console.log('[' + lib + ']');
console.log(' gzip: ' + leftPad(formatNumber(sizeInfo.gzipped), 8) + ' bytes');
console.log(' min: ' + leftPad(formatNumber(sizeInfo.min), 8) + ' bytes');
console.log("[" + lib + "]");
console.log(
" gzip: " + leftPad(formatNumber(sizeInfo.gzipped), 8) + " bytes"
);
console.log(
" min: " + leftPad(formatNumber(sizeInfo.min), 8) + " bytes"
);
console.log();
}
console.log('Minification complete.');
console.log("Minification complete.");
});

View File

@ -1,65 +1,70 @@
{
"name": "size-benchmark",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"setup": "npm install --silent && npm link ../../",
"build": "npm run bundle --silent && npm run minify --silent",
"build-marko": "npm run bundle-marko --silent && node minify.js marko",
"build-vue": "npm run bundle-vue --silent && node minify.js vue",
"build-react": "npm run bundle-react --silent && node minify.js react",
"build-inferno": "npm run bundle-inferno --silent && node minify.js inferno",
"bundle": "mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact && npm run bundle-inferno",
"bundle-marko": "node ../../scripts/build src && NODE_ENV=production rollup -c marko/rollup.config.js",
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"bundle-preact": "NODE_ENV=production rollup -c preact/rollup.config.js",
"bundle-vue": "NODE_ENV=production rollup -c vue/rollup.config.js",
"bundle-inferno": "NODE_ENV=production rollup -c inferno/rollup.config.js",
"minify": "node minify.js",
"http-server": "http-server"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"license": "MIT",
"dependencies": {
"babel-plugin-inferno": "^1.8.0",
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
"babel-plugin-transform-react-constant-elements": "^6.9.1",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-loose": "^8.0.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"babelify": "^7.3.0",
"envify": "^4.0.0",
"format-number": "^2.0.1",
"google-closure-compiler-js": "^20161201.0.0",
"http-server": "^0.9.0",
"inferno": "^1.3.0-rc.1",
"inferno-component": "^1.3.0-rc.1",
"inferno-server": "^1.3.0-rc.1",
"markoify": "^2.1.1",
"minprops": "^1.0.0",
"preact": "^7.1.0",
"react": "^15.4.1",
"react-dom": "^15.4.1",
"rollup": "^0.41.6",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-browserify-transform": "^0.1.0",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-marko": "0.0.2",
"rollup-plugin-node-resolve": "^3.0.0",
"uglify-js": "^2.7.5",
"vue": "^2.1.6",
"vueify": "^9.4.0"
},
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"browser": {
"events": "events-light"
},
"devDependencies": {}
"name": "size-benchmark",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"setup": "npm install --silent && npm link ../../",
"build": "npm run bundle --silent && npm run minify --silent",
"build-marko": "npm run bundle-marko --silent && node minify.js marko",
"build-vue": "npm run bundle-vue --silent && node minify.js vue",
"build-react": "npm run bundle-react --silent && node minify.js react",
"build-inferno":
"npm run bundle-inferno --silent && node minify.js inferno",
"bundle":
"mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact && npm run bundle-inferno",
"bundle-marko":
"node ../../scripts/build src && NODE_ENV=production rollup -c marko/rollup.config.js",
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"bundle-preact":
"NODE_ENV=production rollup -c preact/rollup.config.js",
"bundle-vue": "NODE_ENV=production rollup -c vue/rollup.config.js",
"bundle-inferno":
"NODE_ENV=production rollup -c inferno/rollup.config.js",
"minify": "node minify.js",
"http-server": "http-server"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"license": "MIT",
"dependencies": {
"babel-plugin-inferno": "^1.8.0",
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
"babel-plugin-transform-react-constant-elements": "^6.9.1",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-loose": "^8.0.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"babelify": "^7.3.0",
"envify": "^4.0.0",
"format-number": "^2.0.1",
"google-closure-compiler-js": "^20161201.0.0",
"http-server": "^0.9.0",
"inferno": "^1.3.0-rc.1",
"inferno-component": "^1.3.0-rc.1",
"inferno-server": "^1.3.0-rc.1",
"markoify": "^2.1.1",
"minprops": "^1.0.0",
"preact": "^7.1.0",
"react": "^15.4.1",
"react-dom": "^15.4.1",
"rollup": "^0.41.6",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-browserify-transform": "^0.1.0",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-marko": "0.0.2",
"rollup-plugin-node-resolve": "^3.0.0",
"uglify-js": "^2.7.5",
"vue": "^2.1.6",
"vueify": "^9.4.0"
},
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"browser": {
"events": "events-light"
},
"devDependencies": {}
}

View File

@ -1,35 +1,34 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import babelPlugin from 'rollup-plugin-babel';
import envify from 'envify';
import path from 'path';
import commonjsPlugin from "rollup-plugin-commonjs";
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import babelPlugin from "rollup-plugin-babel";
import envify from "envify";
import path from "path";
process.env.NODE_ENV = 'production';
process.env.NODE_ENV = "production";
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.jsx'),
format: 'iife',
moduleName: 'app',
entry: path.join(__dirname, "client.jsx"),
format: "iife",
moduleName: "app",
plugins: [
babelPlugin({
// include: ['node_modules/**', '**/*.js', '**/*.jsx']
}),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.jsx' ]
extensions: [".js", ".jsx"]
}),
commonjsPlugin({
include: [],
extensions: [ '.js', '.jsx' ]
extensions: [".js", ".jsx"]
})
],
dest: path.join(__dirname, '../build/bundles/preact.js')
dest: path.join(__dirname, "../build/bundles/preact.js")
};

View File

@ -1,35 +1,34 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import babelPlugin from 'rollup-plugin-babel';
import envify from 'envify';
import path from 'path';
import commonjsPlugin from "rollup-plugin-commonjs";
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import babelPlugin from "rollup-plugin-babel";
import envify from "envify";
import path from "path";
process.env.NODE_ENV = 'production';
process.env.NODE_ENV = "production";
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.jsx'),
format: 'iife',
moduleName: 'app',
entry: path.join(__dirname, "client.jsx"),
format: "iife",
moduleName: "app",
plugins: [
babelPlugin({
exclude: 'node_modules/**'
exclude: "node_modules/**"
}),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.jsx' ]
extensions: [".js", ".jsx"]
}),
commonjsPlugin({
include: [],
extensions: [ '.js', '.jsx' ]
extensions: [".js", ".jsx"]
})
],
dest: path.join(__dirname, '../build/bundles/react.js')
dest: path.join(__dirname, "../build/bundles/react.js")
};

View File

@ -1,5 +1,5 @@
var Vue = require('vue');
var App = require('./components/App');
var Vue = require("vue");
var App = require("./components/App");
// var app = new App({
// el: document.body,
@ -11,7 +11,7 @@ var App = require('./components/App');
new Vue({
el: document.body,
render: function (createElement) {
render: function(createElement) {
return createElement(App);
}
});
});

View File

@ -1,33 +1,32 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import vueify from 'vueify';
import envify from 'envify';
import minpropsify from 'minprops/browserify';
import path from 'path';
process.env.NODE_ENV = 'production';
import commonjsPlugin from "rollup-plugin-commonjs";
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import vueify from "vueify";
import envify from "envify";
import minpropsify from "minprops/browserify";
import path from "path";
process.env.NODE_ENV = "production";
export default {
entry: path.join(__dirname, 'client.js'),
format: 'iife',
moduleName: 'app',
entry: path.join(__dirname, "client.js"),
format: "iife",
moduleName: "app",
plugins: [
browserifyPlugin(vueify),
browserifyPlugin(envify),
browserifyPlugin(minpropsify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.vue' ]
extensions: [".js", ".vue"]
}),
commonjsPlugin({
include: [],
extensions: [ '.js', '.vue' ]
extensions: [".js", ".vue"]
})
],
dest: path.join(__dirname, '../build/bundles/vue.js')
dest: path.join(__dirname, "../build/bundles/vue.js")
};

5
benchmark/vdom/.eslintrc Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"browser": true
}
}

View File

@ -1,24 +1,24 @@
module.exports = function(app) {
var Suite = window.Benchmark.Suite;
var names = [
'dom',
'dom-innerHTML',
'marko-vdom',
'react'
];
var names = ["dom", "dom-innerHTML", "marko-vdom", "react"];
var htmlFiles = ['todomvc', 'marko-docs', 'tabs'];
var htmlFiles = ["todomvc", "marko-docs", "tabs"];
function loadScripts() {
window.createBenchmarks = {};
var scripts = [];
names.forEach(function(name) {
htmlFiles.forEach(function(htmlFile) {
scripts.push('./codegen-create/benchmark-' + htmlFile + '-' + name + '.js');
scripts.push(
"./codegen-create/benchmark-" +
htmlFile +
"-" +
name +
".js"
);
});
});
@ -26,18 +26,17 @@ module.exports = function(app) {
}
function runForHtmlFile(htmlFile) {
return loadScripts(htmlFile)
.then(function() {
var suite = new Suite('create-' + htmlFile);
return loadScripts(htmlFile).then(function() {
var suite = new Suite("create-" + htmlFile);
names.forEach(function(name) {
suite.add(name, function() {
return window.createBenchmarks[htmlFile + '-' + name]();
});
names.forEach(function(name) {
suite.add(name, function() {
return window.createBenchmarks[htmlFile + "-" + name]();
});
return app.runSuite(suite);
});
return app.runSuite(suite);
});
}
var loadScriptsPromise = loadScripts();

View File

@ -2,16 +2,15 @@ module.exports = function(app) {
var Suite = window.Benchmark.Suite;
var MarkoVDOM = app.vdom;
var suite = new Suite('walk');
var suite = new Suite("walk");
var todomvcDOM = document.getElementById('todoapp');
var todomvcDOM = document.getElementById("todoapp");
var todomvcDOMVirtual = MarkoVDOM.virtualize(todomvcDOM);
function toHTML(node) {
// NOTE: We don't use XMLSerializer because we need to sort the attributes to correctly compare output HTML strings
// BAD: return (new XMLSerializer()).serializeToString(node);
var html = '';
var html = "";
function serializeHelper(node, indent) {
if (node.nodeType === 1) {
serializeElHelper(node, indent);
@ -20,7 +19,7 @@ module.exports = function(app) {
} else if (node.nodeType === 8) {
serializeCommentHelper(node, indent);
} else {
console.log('Invalid node:', node);
console.log("Invalid node:", node);
html += indent + `INVALID NODE TYPE ${node.nodeType}\n`;
// throw new Error('Unexpected node type');
}
@ -29,66 +28,68 @@ module.exports = function(app) {
function serializeElHelper(el, indent) {
var tagName = el.nodeName;
if (el.namespaceURI === 'http://www.w3.org/2000/svg') {
tagName = 'svg:' + tagName;
} else if (el.namespaceURI === 'http://www.w3.org/1998/Math/MathML') {
tagName = 'math:' + tagName;
if (el.namespaceURI === "http://www.w3.org/2000/svg") {
tagName = "svg:" + tagName;
} else if (
el.namespaceURI === "http://www.w3.org/1998/Math/MathML"
) {
tagName = "math:" + tagName;
}
html += indent + '<' + tagName;
html += indent + "<" + tagName;
var attributes = el.attributes;
var attributesArray = [];
for (var i=0; i<attributes.length; i++) {
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
var attrName = attr.name;
if (attr.namespaceURI) {
attrName = attr.namespaceURI + ':' + attrName;
attrName = attr.namespaceURI + ":" + attrName;
}
attributesArray.push(' ' + attrName + '="' + attr.value + '"');
attributesArray.push(" " + attrName + '="' + attr.value + '"');
}
attributesArray.sort();
html += attributesArray.join('');
html += attributesArray.join("");
html += '>\n';
html += ">\n";
if (tagName.toUpperCase() === 'TEXTAREA') {
html += indent + ' VALUE: ' + JSON.stringify(el.value) + '\n';
if (tagName.toUpperCase() === "TEXTAREA") {
html += indent + " VALUE: " + JSON.stringify(el.value) + "\n";
} else {
var curChild = el.firstChild;
while(curChild) {
serializeHelper(curChild, indent + ' ');
while (curChild) {
serializeHelper(curChild, indent + " ");
curChild = curChild.nextSibling;
}
}
}
function serializeTextHelper(node, indent) {
html += indent + JSON.stringify(node.nodeValue) + '\n';
html += indent + JSON.stringify(node.nodeValue) + "\n";
}
function serializeCommentHelper(node, indent) {
html += indent + '<!--' + JSON.stringify(node.nodeValue) + '-->\n';
html += indent + "<!--" + JSON.stringify(node.nodeValue) + "-->\n";
}
serializeHelper(node, '');
serializeHelper(node, "");
return html;
}
// add tests
suite.add('real DOM', function() {
suite.add("real DOM", function() {
return toHTML(todomvcDOM);
});
suite.add('marko-vdom', function() {
suite.add("marko-vdom", function() {
return toHTML(todomvcDOMVirtual);
});
return function() {
return app.runSuite(suite);
};
};
};

View File

@ -6,13 +6,15 @@
},
{
"type": "js",
"url": "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.js"
"url":
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.js"
},
{
"type": "js",
"url": "https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.2/benchmark.js"
"url":
"https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.2/benchmark.js"
},
"benchmark/benchmark.js",
"require-run: ./client.js"
]
}
}

View File

@ -1,17 +1,17 @@
var HTMLElement = require('../../runtime/vdom/HTMLElement');
var Text = require('../../runtime/vdom/Text');
var Comment = require('../../runtime/vdom/Comment');
var DocumentFragment = require('../../runtime/vdom/DocumentFragment');
var HTMLElement = require("../../runtime/vdom/HTMLElement");
var Text = require("../../runtime/vdom/Text");
var Comment = require("../../runtime/vdom/Comment");
var DocumentFragment = require("../../runtime/vdom/DocumentFragment");
var resultsEl = document.getElementById('results');
var resultsEl = document.getElementById("results");
var running = false;
var benchmarks = {};
function loadScript(path) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
var script = document.createElement("script");
script.src = path;
script.onload = function () {
script.onload = function() {
resolve();
};
@ -24,9 +24,11 @@ function loadScript(path) {
}
function loadScripts(paths) {
return Promise.all(paths.map(function(path) {
return loadScript(path);
}));
return Promise.all(
paths.map(function(path) {
return loadScript(path);
})
);
}
function runSuite(suite) {
@ -38,33 +40,36 @@ function runSuite(suite) {
running = true;
suite
.on('start', function(event) {
.on("start", function() {
resultsEl.innerHTML += 'Running "' + suite.name + '"...\n';
})
.on('cycle', function(event) {
resultsEl.innerHTML += String(event.target) + '\n';
.on("cycle", function(event) {
resultsEl.innerHTML += String(event.target) + "\n";
})
.on('complete', function() {
resultsEl.innerHTML += 'Fastest is ' + this.filter('fastest').map('name') + '\n\n--------------\n\n';
.on("complete", function() {
resultsEl.innerHTML +=
"Fastest is " +
this.filter("fastest").map("name") +
"\n\n--------------\n\n";
running = false;
suite.off('start cycle complete');
suite.off("start cycle complete");
resolve();
})
.on('error', function(e) {
.on("error", function(e) {
running = false;
suite.off('start cycle complete error');
suite.off("start cycle complete error");
reject(e.target.error);
})
// run async
.run({ 'async': true });
.run({ async: true });
});
}
var vdom = window.MarkoVDOM = {
virtualize: require('../../runtime/vdom/virtualize'),
var vdom = (window.MarkoVDOM = {
virtualize: require("../../runtime/vdom/virtualize"),
createElement: function(tagName, attrs, childCount, constId) {
return new HTMLElement(tagName, attrs, childCount, constId);
@ -78,7 +83,7 @@ var vdom = window.MarkoVDOM = {
createDocumentFragment: function() {
return new DocumentFragment();
}
};
});
var app = {
loadScript,
@ -91,19 +96,19 @@ function registerBenchmark(name, func) {
benchmarks[name] = func(app);
}
registerBenchmark('create', require('./benchmark-create'));
registerBenchmark('walk', require('./benchmark-walk'));
registerBenchmark("create", require("./benchmark-create"));
registerBenchmark("walk", require("./benchmark-walk"));
document.body.addEventListener('click', function(event) {
document.body.addEventListener("click", function(event) {
if (running) {
return;
}
var target = event.target;
var benchmarkName = target.getAttribute('data-benchmark');
var benchmarkName = target.getAttribute("data-benchmark");
if (benchmarkName) {
var oldButtonLabel = target.innerHTML;
target.innerHTML = oldButtonLabel + ' - running...';
resultsEl.innerHTML = '';
target.innerHTML = oldButtonLabel + " - running...";
resultsEl.innerHTML = "";
var benchmarkFunc = benchmarks[benchmarkName];
@ -111,7 +116,7 @@ document.body.addEventListener('click', function(event) {
.then(function() {
target.innerHTML = oldButtonLabel;
resultsEl.innerHTML += '\nDONE!';
resultsEl.innerHTML += "\nDONE!";
})
.catch(function(e) {
target.innerHTML = oldButtonLabel;
@ -119,4 +124,4 @@ document.body.addEventListener('click', function(event) {
resultsEl.innerHTML = e.toString();
});
}
});
});

View File

@ -1,4 +1,4 @@
module.exports = function(node, html) {
module.exports = function() {
return `
var fragment = range.createContextualFragment(html);
return fragment.childNodes[0];`;
@ -10,4 +10,4 @@ var range = document.createRange();
range.selectNode(document.body);
var html = ${JSON.stringify(html)};
`;
};
};

View File

@ -1,29 +1,33 @@
module.exports = function(node) {
var nextId = 0;
var code = '';
var code = "";
function codegenEl(node, level) {
var varName = level === 0 ? 'root' : `node${nextId++}`;
var varName = level === 0 ? "root" : `node${nextId++}`;
code += `var ${varName} = document.createElement(${JSON.stringify(node.nodeName)})\n`;
code += `var ${varName} = document.createElement(${JSON.stringify(
node.nodeName
)})\n`;
var attributes = node.attributes;
for (var i=0; i<attributes.length; i++) {
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
code += `${varName}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(attr.value)})\n`;
code += `${varName}.setAttribute(${JSON.stringify(
attr.name
)}, ${JSON.stringify(attr.value)})\n`;
}
var curChild = node.firstChild;
while(curChild) {
while (curChild) {
if (curChild.nodeType === 1) {
var childVarName = codegenEl(curChild, level + 1);
code += `${varName}.appendChild(${childVarName})\n`;
} else if (curChild.nodeType === 3) {
code += `${varName}.appendChild(document.createTextNode(${JSON.stringify(curChild.nodeValue)}))\n`;
code += `${varName}.appendChild(document.createTextNode(${JSON.stringify(
curChild.nodeValue
)}))\n`;
}
curChild = curChild.nextSibling;
@ -34,7 +38,7 @@ module.exports = function(node) {
codegenEl(node, 0);
code += '\nreturn root;\n';
code += "\nreturn root;\n";
return code;
};
};

View File

@ -1,40 +1,42 @@
function indentStr(level) {
var str = '';
for (var i=0; i<level; i++) {
str += ' ';
var str = "";
for (var i = 0; i < level; i++) {
str += " ";
}
return str;
}
module.exports = function(node) {
var code = '';
var code = "";
function codegenEl(node, level) {
if (level === 0) {
code += 'createElement';
code += "createElement";
} else {
code += '.e';
code += ".e";
}
var attrsMap = {};
var attributes = node.attributes;
for (var i=0; i<attributes.length; i++) {
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
attrsMap[attr.name] = attr.value;
}
code += `(${JSON.stringify(node.nodeName)}, ${JSON.stringify(attrsMap)}, ${node.childNodes.length})\n`;
code += `(${JSON.stringify(node.nodeName)}, ${JSON.stringify(
attrsMap
)}, ${node.childNodes.length})\n`;
// codegenAttrs(node.attributes, level + 1);
var curChild = node.firstChild;
while(curChild) {
while (curChild) {
codegen(curChild, level + 1);
curChild = curChild.nextSibling;
}
}
function codegenText(node, level) {
function codegenText(node) {
code += `.t(${JSON.stringify(node.nodeValue)})\n`;
}
@ -50,14 +52,12 @@ module.exports = function(node) {
codegen(node, 0);
return 'return ' + code + '\n';
return "return " + code + "\n";
};
module.exports.generateInitCode = function(node, html) {
module.exports.generateInitCode = function() {
return `
var MarkoVDOM = window.MarkoVDOM;
var createElement = MarkoVDOM.createElement;
`;
};

View File

@ -1,7 +1,7 @@
function indentStr(level) {
var str = '';
for (var i=0; i<level; i++) {
str += ' ';
var str = "";
for (var i = 0; i < level; i++) {
str += " ";
}
return str;
@ -9,54 +9,55 @@ function indentStr(level) {
module.exports = function(node) {
function codegenChildNodes(node, level) {
var curChild = node.firstChild;
if (!curChild) {
return 'null';
return "null";
}
var code = '[\n';
var code = "[\n";
var childLevel = level + 2;
while(curChild) {
while (curChild) {
code += indentStr(childLevel) + codegen(curChild, childLevel);
curChild = curChild.nextSibling;
if (curChild) {
code += ',\n';
code += ",\n";
} else {
code += '\n';
code += "\n";
}
}
code += indentStr(level + 1) + ']';
code += indentStr(level + 1) + "]";
return code;
}
function codegenEl(node, level) {
var attributesMap = null;
var attributes = node.attributes;
if (attributes.length) {
attributesMap = {};
for (var i=0; i<attributes.length; i++) {
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
var attrName = attr.name;
var attrValue = attr.value;
if (attrName === 'class') {
attrName = 'className';
if (attrName === "class") {
attrName = "className";
}
attributesMap[attrName] = attrValue;
}
}
return `React.createElement(${JSON.stringify(node.nodeName)}, ${JSON.stringify(attributesMap)}, ${codegenChildNodes(node, level)})`;
return `React.createElement(${JSON.stringify(
node.nodeName
)}, ${JSON.stringify(attributesMap)}, ${codegenChildNodes(
node,
level
)})`;
}
function codegen(node, level) {
@ -67,5 +68,5 @@ module.exports = function(node) {
}
}
return 'return ' + codegen(node, 0) + '\n';
};
return "return " + codegen(node, 0) + "\n";
};

View File

@ -1,13 +1,14 @@
var jsdom = require("jsdom").jsdom;
var fs = require('fs');
var path = require('path');
var fs = require("fs");
var path = require("path");
function generateCode(name, htmlFile, rootNode, html) {
var generator = require('./codegen-' + name);
var generator = require("./codegen-" + name);
var code = generator(rootNode, html);
var wrappedCode = `window.createBenchmarks[${JSON.stringify(htmlFile+'-'+name)}]=function() {\n${code}\n}`;
var wrappedCode = `window.createBenchmarks[${JSON.stringify(
htmlFile + "-" + name
)}]=function() {\n${code}\n}`;
var generateInitCode = generator.generateInitCode;
@ -18,24 +19,24 @@ ${generateInitCode(rootNode, html)}
${wrappedCode}
}())`;
}
fs.writeFileSync(path.join(__dirname, `benchmark-${htmlFile}-${name}.js`), wrappedCode, { encoding: 'utf8' });
fs.writeFileSync(
path.join(__dirname, `benchmark-${htmlFile}-${name}.js`),
wrappedCode,
{ encoding: "utf8" }
);
}
var methods = [
'dom',
'dom-innerHTML',
'marko-vdom',
'react'
];
var methods = ["dom", "dom-innerHTML", "marko-vdom", "react"];
var htmlFiles = fs.readdirSync(__dirname)
.filter(function(name) {
return name.startsWith('html-');
});
var htmlFiles = fs.readdirSync(__dirname).filter(function(name) {
return name.startsWith("html-");
});
htmlFiles.forEach(function(htmlFile) {
var name = htmlFile.substring('html-'.length).slice(0, 0-'.html'.length);
var html = fs.readFileSync(path.join(__dirname, htmlFile), { encoding: 'utf8' });
var name = htmlFile.substring("html-".length).slice(0, 0 - ".html".length);
var html = fs.readFileSync(path.join(__dirname, htmlFile), {
encoding: "utf8"
});
var doc = jsdom(html);

View File

@ -1,32 +1,32 @@
require('../patch-module');
require("../patch-module");
require('marko/node-require');
require('marko/express');
require("marko/node-require");
require("marko/express");
var isProduction = process.env.NODE_ENV === 'production';
var isProduction = process.env.NODE_ENV === "production";
require('lasso').configure({
outputDir: __dirname + '/static',
require("lasso").configure({
outputDir: __dirname + "/static",
bundlingEnabled: isProduction,
fingerprintsEnabled: isProduction,
minify: isProduction
});
var express = require('express');
var express = require("express");
var app = express();
var serveStatic = require('serve-static');
var serveStatic = require("serve-static");
require('./codegen-create/run');
require("./codegen-create/run");
var template = require('./index.marko');
var template = require("./index.marko");
app.use('/codegen-create', serveStatic(__dirname + '/codegen-create'));
app.use("/codegen-create", serveStatic(__dirname + "/codegen-create"));
app.use(require('lasso/middleware').serveStatic());
app.use(require("lasso/middleware").serveStatic());
app.get('/', function(req, res) {
app.get("/", function(req, res) {
res.marko(template);
});
@ -35,5 +35,5 @@ app.listen(8080, function(err) {
throw err;
}
console.log('Server ready:\nhttp://localhost:8080');
console.log("Server ready:\nhttp://localhost:8080");
});

View File

@ -1,36 +1,42 @@
var fs = require('fs');
var nodePath = require('path');
/* eslint-disable no-console */
var fs = require("fs");
var nodePath = require("path");
var cwd = process.cwd();
var resolveFrom = require('resolve-from');
var resolveFrom = require("resolve-from");
// Try to use the Marko compiler installed with the project
var markoCompilerPath;
const markocPkgVersion = require('../package.json').version;
const markocPkgVersion = require("../package.json").version;
var markoPkgVersion;
try {
var markoPkgPath = resolveFrom(process.cwd(), 'marko/package.json');
var markoPkgPath = resolveFrom(process.cwd(), "marko/package.json");
markoPkgVersion = require(markoPkgPath).version;
} catch(e) {}
} catch (e) {
/* ignore error */
}
try {
markoCompilerPath = resolveFrom(process.cwd(), 'marko/compiler');
} catch(e) {}
markoCompilerPath = resolveFrom(process.cwd(), "marko/compiler");
} catch (e) {
/* ignore error */
}
var markoCompiler;
if (markoCompilerPath) {
markoCompiler = require(markoCompilerPath);
} else {
markoCompiler = require('../compiler');
markoCompiler = require("../compiler");
}
var Minimatch = require('minimatch').Minimatch;
var Minimatch = require("minimatch").Minimatch;
var appModulePath = require('app-module-path');
var appModulePath = require("app-module-path");
require('raptor-polyfill/string/startsWith');
require('raptor-polyfill/string/endsWith');
require("raptor-polyfill/string/startsWith");
require("raptor-polyfill/string/endsWith");
markoCompiler.defaultOptions.checkUpToDate = false;
@ -42,58 +48,65 @@ var mmOptions = {
function relPath(path) {
if (path.startsWith(cwd)) {
return path.substring(cwd.length+1);
return path.substring(cwd.length + 1);
}
}
var args = require('argly').createParser({
'--help': {
type: 'boolean',
description: 'Show this help message'
var args = require("argly")
.createParser({
"--help": {
type: "boolean",
description: "Show this help message"
},
'--files --file -f *': {
type: 'string[]',
description: 'A set of directories or files to compile'
"--files --file -f *": {
type: "string[]",
description: "A set of directories or files to compile"
},
'--ignore -i': {
type: 'string[]',
description: 'An ignore rule (default: --ignore "/node_modules" ".*")'
"--ignore -i": {
type: "string[]",
description:
'An ignore rule (default: --ignore "/node_modules" ".*")'
},
'--clean -c': {
type: 'boolean',
description: 'Clean all of the *.marko.js files'
"--clean -c": {
type: "boolean",
description: "Clean all of the *.marko.js files"
},
'--force': {
type: 'boolean',
description: 'Force template recompilation even if unchanged'
"--force": {
type: "boolean",
description: "Force template recompilation even if unchanged"
},
'--paths -p': {
type: 'string[]',
description: 'Additional directories to add to the Node.js module search path'
"--paths -p": {
type: "string[]",
description:
"Additional directories to add to the Node.js module search path"
},
'--vdom -V': {
type: 'boolean',
description: 'VDOM output'
"--vdom -V": {
type: "boolean",
description: "VDOM output"
},
'--version -v': {
type: 'boolean',
description: 'Print markoc and marko compiler versions to the console'
"--version -v": {
type: "boolean",
description:
"Print markoc and marko compiler versions to the console"
}
})
.usage('Usage: $0 <pattern> [options]')
.example('Compile a single template', '$0 template.marko')
.example('Compile all templates in the current directory', '$0 .')
.example('Compile multiple templates', '$0 template.marko src/ foo/')
.example('Delete all *.marko.js files in the current directory', '$0 . --clean')
.usage("Usage: $0 <pattern> [options]")
.example("Compile a single template", "$0 template.marko")
.example("Compile all templates in the current directory", "$0 .")
.example("Compile multiple templates", "$0 template.marko src/ foo/")
.example(
"Delete all *.marko.js files in the current directory",
"$0 . --clean"
)
.validate(function(result) {
if (result.help) {
this.printUsage();
process.exit(0);
} else if (result.version) {
console.log('markoc@' + markocPkgVersion);
console.log("markoc@" + markocPkgVersion);
if (markoPkgVersion) {
console.log('marko@' + markoPkgVersion);
console.log("marko@" + markoPkgVersion);
}
process.exit(0);
@ -114,20 +127,19 @@ var args = require('argly').createParser({
})
.parse();
var output = 'html';
var output = "html";
var isForBrowser = false;
if (args.vdom) {
output = 'vdom';
output = "vdom";
isForBrowser = true;
}
var compileOptions = {
output: output,
browser: isForBrowser,
compilerType: 'markoc',
compilerType: "markoc",
compilerVersion: markoPkgVersion || markocPkgVersion
};
@ -146,30 +158,28 @@ if (paths && paths.length) {
var ignoreRules = args.ignore;
if (!ignoreRules) {
ignoreRules = ['/node_modules', '.*'];
ignoreRules = ["/node_modules", ".*"];
}
ignoreRules = ignoreRules.filter(function (s) {
ignoreRules = ignoreRules.filter(function(s) {
s = s.trim();
return s && !s.match(/^#/);
});
ignoreRules = ignoreRules.map(function (pattern) {
ignoreRules = ignoreRules.map(function(pattern) {
return new Minimatch(pattern, mmOptions);
});
function isIgnored(path, dir, stat) {
if (path.startsWith(dir)) {
path = path.substring(dir.length);
}
path = path.replace(/\\/g, '/');
path = path.replace(/\\/g, "/");
var ignore = false;
var ignoreRulesLength = ignoreRules.length;
for (var i=0; i<ignoreRulesLength; i++) {
for (var i = 0; i < ignoreRulesLength; i++) {
var rule = ignoreRules[i];
var match = rule.match(path);
@ -177,14 +187,15 @@ function isIgnored(path, dir, stat) {
if (!match && stat && stat.isDirectory()) {
try {
stat = fs.statSync(path);
} catch(e) {}
} catch (e) {
/* ignore error */
}
if (stat && stat.isDirectory()) {
match = rule.match(path + '/');
match = rule.match(path + "/");
}
}
if (match) {
if (rule.negate) {
ignore = false;
@ -199,7 +210,7 @@ function isIgnored(path, dir, stat) {
function walk(files, options, done) {
if (!files || files.length === 0) {
done('No files provided');
done("No files provided");
}
var pending = 0;
@ -227,7 +238,6 @@ function walk(files, options, done) {
} else {
done(null);
}
}
}
};
@ -266,7 +276,7 @@ function walk(files, options, done) {
});
}
for (var i=0; i<files.length; i++) {
for (var i = 0; i < files.length; i++) {
var file = nodePath.resolve(cwd, files[i]);
var stat = fs.statSync(file);
@ -288,32 +298,34 @@ if (args.clean) {
file: function(file, context) {
var basename = nodePath.basename(file);
if (basename.endsWith('.marko.js') || basename.endsWith('.marko.html') || basename.endsWith('.marko.xml.js')) {
if (
basename.endsWith(".marko.js") ||
basename.endsWith(".marko.html") ||
basename.endsWith(".marko.xml.js")
) {
context.beginAsync();
fs.unlink(file, function(err) {
if (err) {
return context.endAsync(err);
}
deleteCount++;
console.log('Deleted: ' + file);
console.log("Deleted: " + file);
context.endAsync();
});
}
}
},
function(err) {
function() {
if (deleteCount === 0) {
console.log('No *.marko.js files were found. Already clean.');
console.log("No *.marko.js files were found. Already clean.");
} else {
console.log('Deleted ' + deleteCount + ' file(s)');
console.log("Deleted " + deleteCount + " file(s)");
}
});
}
);
} else {
var found = {};
var compileCount = 0;
var failed;
var failed = [];
var compile = function(path, context) {
@ -322,37 +334,51 @@ if (args.clean) {
}
found[path] = true;
var outPath = path + '.js';
var outPath = path + ".js";
console.log('Compiling:\n Input: ' + relPath(path) + '\n Output: ' + relPath(outPath) + '\n');
console.log(
"Compiling:\n Input: " +
relPath(path) +
"\n Output: " +
relPath(outPath) +
"\n"
);
context.beginAsync();
markoCompiler.compileFile(path, compileOptions, function(err, src) {
if (err) {
failed.push('Failed to compile "' + relPath(path) + '". Error: ' + (err.stack || err));
failed.push(
'Failed to compile "' +
relPath(path) +
'". Error: ' +
(err.stack || err)
);
context.endAsync(err);
return;
}
context.beginAsync();
fs.writeFile(outPath, src, {encoding: 'utf8'}, function(err, src) {
fs.writeFile(outPath, src, { encoding: "utf8" }, function(err) {
if (err) {
failed.push('Failed to write "' + path + '". Error: ' + (err.stack || err));
failed.push(
'Failed to write "' +
path +
'". Error: ' +
(err.stack || err)
);
context.endAsync(err);
return;
}
compileCount++;
context.endAsync();
});
context.endAsync();
});
};
if (args.files && args.files.length) {
walk(
args.files,
@ -360,7 +386,11 @@ if (args.clean) {
file: function(file, context) {
var basename = nodePath.basename(file);
if (basename.endsWith('.marko') || basename.endsWith('.marko.html') || basename.endsWith('.marko.xml')) {
if (
basename.endsWith(".marko") ||
basename.endsWith(".marko.html") ||
basename.endsWith(".marko.xml")
) {
compile(file, context);
}
}
@ -368,7 +398,10 @@ if (args.clean) {
function(err) {
if (err) {
if (failed.length) {
console.error('The following errors occurred:\n- ' + failed.join('\n- '));
console.error(
"The following errors occurred:\n- " +
failed.join("\n- ")
);
} else {
console.error(err);
}
@ -377,11 +410,11 @@ if (args.clean) {
}
if (compileCount === 0) {
console.log('No templates found');
console.log("No templates found");
} else {
console.log('Compiled ' + compileCount + ' templates(s)');
console.log("Compiled " + compileCount + " templates(s)");
}
});
}
);
}
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/browser-refresh');
module.exports = require("./src/browser-refresh");
} else {
module.exports = require('./dist/browser-refresh');
module.exports = require("./dist/browser-refresh");
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/compiler');
module.exports = require("./src/compiler");
} else {
module.exports = require('./dist/compiler');
module.exports = require("./dist/compiler");
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/components');
module.exports = require("./src/components");
} else {
module.exports = require('./dist/components');
module.exports = require("./dist/components");
}

View File

@ -127,7 +127,7 @@ button.example-button on-click('increment') — Click me!
Cant make up your mind or just want to paste in that code snippet from
StackOverflow? HTML syntax can be used within in the concise syntax. Youll come
back and make it consistent…*one day*.
back and make it consistent…_one day_.
#### 5. Import JavaScript modules
@ -174,8 +174,8 @@ imported:
#### 7. Use JavaScript to set CSS classes and styles
Setting CSS classes and styles is made easy using JavaScript! Marko will happily
accept simple strings, JavaScript objects and arrays (*falsy values will be
ignored).*
accept simple strings, JavaScript objects and arrays (_falsy values will be
ignored)._
```marko
$ const fontColor = input.color || 'blue';
@ -243,23 +243,29 @@ components](http://markojs.com/docs/server-side-rendering/) rendered on the
server when the page loads in the browser):
```js
require('marko/node-require').install(); // require .marko files!
require("marko/node-require").install(); // require .marko files!
const http = require('http');
const template = require('./template');
const http = require("http");
const template = require("./template");
http.createServer().on('request', (req, res) => {
template.render({
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
}, res);
}).listen(8080);
http
.createServer()
.on("request", (req, res) => {
template.render(
{
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
},
res
);
})
.listen(8080);
```
#### Bonus: Friendly compile-time errors
We all make mistakes *every now and then*. Typo in your custom tag? Forgot an
We all make mistakes _every now and then_. Typo in your custom tag? Forgot an
ending tag? No worries! Marko will give you a friendly error message and point
you right to the problematic code.
@ -276,7 +282,7 @@ Unrecognized tag: fancy-buttn — More details: https://github.com/marko-js/mark
Coming soon: auto correction and autonomous coding
*****
---
*Cover image credit:
*[Wikipedia](https://en.wikipedia.org/wiki/List_of_rock_formations#/media/File:Amanhecer_no_Hercules_--.jpg)
_Cover image credit:
_[Wikipedia](https://en.wikipedia.org/wiki/List_of_rock_formations#/media/File:Amanhecer_no_Hercules_--.jpg)

View File

@ -12,15 +12,14 @@ With Marko, the DOM output of a UI component is based on input properties and a
Marko makes it easy to to co-locate your component's class and styles with the HTML view that they correspond to. The following are the key part of any UI component:
- __View__ - The HTML template for your UI component. Receives input properties and states and renders to either HTML (server-side) or virtual DOM nodes (browser-side)
- __Client-side behavior__ - Implemented as a JavaScript class with methods and properties to provide initialization, event handling (including DOM events, custom events and lifecycle events) and state management
- __Styling__ - Cascading StyleSheet with support for CSS preprocessors such as Less or Sass
* **View** - The HTML template for your UI component. Receives input properties and states and renders to either HTML (server-side) or virtual DOM nodes (browser-side)
* **Client-side behavior** - Implemented as a JavaScript class with methods and properties to provide initialization, event handling (including DOM events, custom events and lifecycle events) and state management
* **Styling** - Cascading StyleSheet with support for CSS preprocessors such as Less or Sass
## Server-side rendering
A UI component can be rendered on the server or in the browser, but in either case, the UI component instance will be mounted to a DOM node in the browser automatically. If a UI component tree is rendered on the server then Marko will automatically recreate the UI component tree in the browser with no extra code required. For more details, please see [Server-side rendering](/docs/server-side-rendering/).
## Single-file components
Marko allows you to define a `class` for a component right in the `.marko` view and call methods of that class with `on-*` attributes:
@ -45,7 +44,7 @@ class {
### Styles
Adding styles to your view is also made easy. These styles won't be output in a `<style>` tag as inline styles usually are, but will result in the style being externalized so it isn't duplicated should a component be used more than once on the page.
Adding styles to your view is also made easy. These styles won't be output in a `<style>` tag as inline styles usually are, but will result in the style being externalized so it isn't duplicated should a component be used more than once on the page.
```marko
style {
@ -61,6 +60,7 @@ style {
```
If you use a css preprocessor, you can add the extension right on `style`:
```marko
style.less {
button.primary {
@ -71,16 +71,15 @@ style.less {
> **Note:** The code in the style section is processed in a context that is separate from the rest of the template so so you won't be able to use JavaScript variables inside the style section. If you need variables in your CSS then you will need to use a CSS pre-processor that supports variables.
## Multi-file components
You might prefer to keep your component's class and style definitions in separate files from the view definition - the classical separation of HTML, CSS and JavaScript. Marko makes this possible with a simple filename based convention.
You might prefer to keep your component's class and style definitions in separate files from the view definition - the classical separation of HTML, CSS and JavaScript. Marko makes this possible with a simple filename based convention.
> **ProTip:** If your motivation to move the component's class and styles out to a separate file is that the code is getting too large, consider splitting the component into smaller, more manageable components.
### Supporting files
Marko automatically discovers supporting files in the same directory as a Marko view. For example if you have a view named `counter.marko`, Marko will automatically look for `counter.component.js` and `counter.style.css`.
Marko automatically discovers supporting files in the same directory as a Marko view. For example if you have a view named `counter.marko`, Marko will automatically look for `counter.component.js` and `counter.style.css`.
```
counter.marko
@ -88,7 +87,7 @@ counter.component.js
counter.style.css
```
Marko also handles views named `index.marko` specially, it will look for `component.js` and `style.css` in addition to `index.component.js` and `index.style.css`. This allows easily grouping component files into a directory:
Marko also handles views named `index.marko` specially, it will look for `component.js` and `style.css` in addition to `index.component.js` and `index.style.css`. This allows easily grouping component files into a directory:
```
counter/
@ -101,18 +100,19 @@ In your `component.js` file, export the component's class:
```js
module.exports = class {
onCreate() {
this.state = {
count:0
};
}
increment() {
this.state.count++;
}
}
onCreate() {
this.state = {
count: 0
};
}
increment() {
this.state.count++;
}
};
```
In your `index.marko` file, you can reference methods from the class using `on-*` attributes:
```marko
<div>
<h2>The current count is ${state.count}</h2>
@ -121,46 +121,46 @@ In your `index.marko` file, you can reference methods from the class using `on-*
```
And in your `style.css`, define the style:
```css
button.primary {
background-color:#09c;
background-color: #09c;
}
```
> **ProTip:** In addition to looking for `[name].style.css`, Marko actually looks for `[name].style.*` so it will also pick up any css preprocessor you're using (less, stylus, scss, etc.).
### Components with plain objects
If you're targeting a browser that does not support classes, a plain object may also be exported:
```js
module.exports = {
onCreate: function() {
this.state = {
count:0
};
},
increment: function() {
this.state.count++;
}
}
onCreate: function() {
this.state = {
count: 0
};
},
increment: function() {
this.state.count++;
}
};
```
## Split components
Split components allow you to optimize for the case where a component is rendered on the server, but doesn't need to be re-rendered in the browser. Because the component doesn't need to be rendered in the browser, the template does not need to be sent to the browser. This can reduce your page weight by a few hundred bytes in some cases.
Split components allow you to optimize for the case where a component is rendered on the server, but doesn't need to be re-rendered in the browser. Because the component doesn't need to be rendered in the browser, the template does not need to be sent to the browser. This can reduce your page weight by a few hundred bytes in some cases.
> **Note:** If a split component is the child of a stateful component, the full rendering logic will still be sent down because the parent component may pass new input to the split component, requiring it to re-render.
Additionally if _all_ components rendered on a page are split components, Marko's VDOM and rendering runtime is not necessary and therefore not sent to the browser, which can further reduce page weight by a few kilobytes.
> **ProTip:** Don't over-optimize. If your component really doesn't need re-rendering, go ahead and split, but don't forgo stateful re-rendering when it would make your code more maintainable.
> **ProTip:** Don't over-optimize. If your component really doesn't need re-rendering, go ahead and split, but don't forgo stateful re-rendering when it would make your code more maintainable.
### Usage
Marko discovers split components in a similar way to how it discovers an external component class:
for example if you have a view named `button.marko`, it will automatically look for `button.component-browser.js`. If your view is named `index.marko`, it will look for `component-browser.js` in addition to `index.component-browser.js`.
for example if you have a view named `button.marko`, it will automatically look for `button.component-browser.js`. If your view is named `index.marko`, it will look for `component-browser.js` in addition to `index.component-browser.js`.
```
counter/
@ -168,10 +168,12 @@ counter/
component-browser.js
```
A split component might also need to do some set up as part of the initial render. In this case, the component may define a second component class to use the `onCreate`, `onInput`, and `onRender` [lifecycle methods](#lifecycle-events). This class can be exported from `component.js` or defined right in the template as with single-file components.
A split component might also need to do some set up as part of the initial render. In this case, the component may define a second component class to use the `onCreate`, `onInput`, and `onRender` [lifecycle methods](#lifecycle-events). This class can be exported from `component.js` or defined right in the template as with single-file components.
### Example
_index.marko_
```marko
class {
onCreate() {
@ -183,12 +185,13 @@ class {
```
_component-browser.js_
```js
module.exports = {
shout() {
alert('My favorite number is ' + this.number + '!');
}
}
shout() {
alert("My favorite number is " + this.number + "!");
}
};
```
## Event handling
@ -226,10 +229,10 @@ class {
Any string that represents a valid JavaScript identifier is allowed for the event handler method name and it can be a JavaScript expression. The following arguments are passed to the handler method when the event is fired:
1. `...args` - Any extra arguments bind are _prepended_ to the arguments passed to the component's handler method
- For example: `on-click('onButtonClick', arg1, arg2)``onButtonClick(arg1, arg2, event, el)`)
2. `event` - The native DOM event
3. `el` - The DOM element that the event listener was attached to
1. `...args` - Any extra arguments bind are _prepended_ to the arguments passed to the component's handler method
* For example: `on-click('onButtonClick', arg1, arg2)``onButtonClick(arg1, arg2, event, el)`)
2. `event` - The native DOM event
3. `el` - The DOM element that the event listener was attached to
When using the `on-*` or `once-*` attributes to attach event listeners, Marko will use event delegation that is more efficient than using `el.addEventListener()` directly. Please see [Why is Marko Fast? » Event delegation](/docs/why-is-marko-fast/#event-delegation) for more details.
@ -256,14 +259,14 @@ class {
Any string that represents a valid JavaScript identifier is allowed for the event handler method name and it can be a JavaScript expression. The following arguments are passed to the handler method when the event is fired:
1. `...args` - Any extra bind arguments are _prepended_ to the arguments passed to the component's handler method
2. `...eventArgs` - The arguments passed to `this.emit()` by the target UI component
3. `component` - The component instance that the event listener was attached to
1. `...args` - Any extra bind arguments are _prepended_ to the arguments passed to the component's handler method
2. `...eventArgs` - The arguments passed to `this.emit()` by the target UI component
3. `component` - The component instance that the event listener was attached to
The following code illustrates how the UI component for `<counter>` might emit the `change` event:
_counter/index.marko_
```marko
class {
onCreate() {
@ -287,12 +290,10 @@ class {
</div>
```
> **ProTip:** Unlike native DOM events, UI component custom events may be emitted with multiple arguments. For example:
```js
this.emit('foo', 'bar', 'baz');
this.emit("foo", "bar", "baz");
```
## Attributes
@ -370,11 +371,12 @@ The `key` attribute can be used to pair HTML elements or UI components that are
Putting the `:scoped` modifier on an attribute will result in the attribute value being prefixed with a unique ID associated with the current UI component. Thus, `:scoped` attribute modifiers can be used to assign a globally unique attribute value from a value that only needs to be unique to the current UI component.
Certain HTML attributes reference the `id` of other elements on the page. For example, the [HTML `<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) `for` attribute takes an `id` as its value. Many `ARIA` attributes ([`aria-describedby`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute), etc.) also take an `id` as their value.
Certain HTML attributes reference the `id` of other elements on the page. For example, the [HTML `<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) `for` attribute takes an `id` as its value. Many `ARIA` attributes ([`aria-describedby`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute), etc.) also take an `id` as their value.
The `:scoped` modifier on an attribute allows you to reference another element as shown in the following examples:
**`for:scoped`**
```marko
<label for:scoped="name">Name</label>
<input id:scoped="name" value="Frank"/>
@ -477,7 +479,7 @@ suffix:
### `this.el`
The root [HTML element](https://developer.mozilla.org/en-US/docs/Web/API/element) that the component is bound to. If there are multiple roots, this is the first.
The root [HTML element](https://developer.mozilla.org/en-US/docs/Web/API/element) that the component is bound to. If there are multiple roots, this is the first.
### `this.els`
@ -489,9 +491,9 @@ The String ID of the root [HTML element](https://developer.mozilla.org/en-US/doc
### `this.state`
The current state for the component. Changing `this.state` or any of its direct properties will result in the component being re-rendered.
The current state for the component. Changing `this.state` or any of its direct properties will result in the component being re-rendered.
Only properties that exist when `this.state` is first defined will be watched for changes. If you don't need a property initially, you can set it to `null`.
Only properties that exist when `this.state` is first defined will be watched for changes. If you don't need a property initially, you can set it to `null`.
```marko
class {
@ -520,7 +522,7 @@ this.state.numbers.push(num);
// mark numbers as dirty, because a `push`
// won't be automatically detected by Marko
this.setStateDirty('numbers');
this.setStateDirty("numbers");
```
### `this.input`
@ -566,18 +568,18 @@ The `state` variable refers to UI component's state object and is the unwatched
### `destroy([options])`
| option | type | default | description |
| ------- | ---- | ------- | ----------- |
| `removeNode` | `Boolean` | `true` | `false` will keep the component in the DOM while still unsubscribing all events from it |
| `recursive` | `Boolean` | `true` | `false` will prevent child components from being destroyed |
| option | type | default | description |
| ------------ | --------- | ------- | --------------------------------------------------------------------------------------- |
| `removeNode` | `Boolean` | `true` | `false` will keep the component in the DOM while still unsubscribing all events from it |
| `recursive` | `Boolean` | `true` | `false` will prevent child components from being destroyed |
Destroys the component by unsubscribing from all listeners made using the `subscribeTo` method and then detaching the component's root element from the DOM. All nested components (discovered by querying the DOM) are also destroyed.
```javascript
component.destroy({
removeNode: true, //true by default
recursive: true //true by default
})
removeNode: true, //true by default
recursive: true //true by default
});
```
### `forceUpdate()`
@ -589,48 +591,48 @@ Queue the component to re-render and skip all checks to see if it actually needs
### `getEl([key, index])`
| params | type | description |
| ------- | ---- | ----------- |
| `key` | `String` | _optional_ the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `HTMLElement` | the element matching the key or `this.el` if no key is provided |
| params | type | description |
| ------------ | ------------- | ------------------------------------------------------------------------------- |
| `key` | `String` | _optional_ the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `HTMLElement` | the element matching the key or `this.el` if no key is provided |
Returns a nested DOM element by prefixing the provided `key` with the component's ID. For Marko, nested DOM elements should be assigned an ID using the `key` custom attribute.
### `getEls(key)`
| params | type | description |
| ------- | ---- | ----------- |
| `key` | `String` | the scoped identifier for the element |
| params | type | description |
| ------------ | -------------------- | ----------------------------------------------------- |
| `key` | `String` | the scoped identifier for the element |
| return value | `Array<HTMLElement>` | an array of _repeated_ DOM elements for the given key |
Repeated DOM elements must have a value for the `key` attribute that ends with `[]` (e.g., `key="items[]"`)
### `getElId([key, index])`
| params | type | description |
| ------- | ---- | ----------- |
| `key` | `String` | _optional_ the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `String` | the element ID matching the key or `this.el.id` if `key` is undefined |
| params | type | description |
| ------------ | -------- | ------------------------------------------------------------------------------- |
| `key` | `String` | _optional_ the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `String` | the element ID matching the key or `this.el.id` if `key` is undefined |
Similar to `getEl`, but only returns the String ID of the nested DOM element instead of the actual DOM element.
### `getComponent(key[, index])`
| params | type | description |
| ------- | ---- | ----------- |
| `key` | `String` | the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| params | type | description |
| ------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key` | `String` | the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `Component` | a reference to a nested `Component` for the given key. If an `index` is provided and the target component is a repeated component (e.g. `key="items[]"`) then the component at the given index will be returned. |
### `getComponents(key, [, index])`
| params | type | description |
| ------- | ---- | ----------- |
| `key` | `String` | the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `Array<Component>` | an array of _repeated_ `Component` instances for the given key |
| params | type | description |
| ------------ | ------------------ | ------------------------------------------------------------------------------- |
| `key` | `String` | the scoped identifier for the element |
| `index` | `Number` | _optional_ the index of the component, if `key` references a repeated component |
| return value | `Array<Component>` | an array of _repeated_ `Component` instances for the given key |
Repeated components must have a value for the `key` attribute that ends with `[]` (e.g., `key="items[]"`)
@ -643,8 +645,8 @@ Returns `true` if a component has been destroyed using
### `replaceState(newState)`
| params | type | description |
| ------- | ---- | ----------- |
| params | type | description |
| ---------- | -------- | ------------------------------------------------ |
| `newState` | `Object` | a new state object to replace the previous state |
Replaces the state with an entirely new state. Equivalent to `this.state = newState`.
@ -653,46 +655,46 @@ Replaces the state with an entirely new state. Equivalent to `this.state = newSt
### `rerender([input])`
| params | type | description |
| ------- | ---- | ----------- |
| params | type | description |
| ------- | -------- | -------------------------------------------------- |
| `input` | `Object` | _optional_ new input data to use when re-rendering |
Rerenders the component using its `renderer` and either supplied `input` or internal `input` and `state`.
### `setState(name, value)`
| params | type | description |
| ------- | ---- | ----------- |
| `name` | `String` | the name of the state property to update |
| `value` | `Any` | the new value for the state property |
| params | type | description |
| ------- | -------- | ---------------------------------------- |
| `name` | `String` | the name of the state property to update |
| `value` | `Any` | the new value for the state property |
Used to change the value of a single state property. Equivalent to setting `this.state[name] = value` except it will also work for adding new properties to the component state.
Used to change the value of a single state property. Equivalent to setting `this.state[name] = value` except it will also work for adding new properties to the component state.
```javascript
this.setState('disabled', true);
this.setState("disabled", true);
```
### `setState(newState)`
| params | type | description |
| ------- | ---- | ----------- |
| params | type | description |
| ---------- | -------- | --------------------------------------------------- |
| `newState` | `Object` | a new state object to merge into the previous state |
Used to change the value of multiple state properties.
```js
this.setState({
disabled: true,
size: 'large'
disabled: true,
size: "large"
});
```
### `setStateDirty(name[, value])`
| params | type | description |
| ------- | ---- | ----------- |
| `name` | `String` | the name of the state property to mark as dirty |
| `value` | `Any` | _optional_ a new value for the state property |
| params | type | description |
| ------- | -------- | ----------------------------------------------- |
| `name` | `String` | the name of the state property to mark as dirty |
| `value` | `Any` | _optional_ a new value for the state property |
Force a state property to be changed even if the value is equal to the old value. This is helpful in cases where a change occurs to a complex object that would not be detected by a shallow compare. Invoking this function completely circumvents all property equality checks (shallow compares) and always rerenders the component.
@ -706,28 +708,29 @@ _example.js_
```javascript
// Because this does not create a new array, the change
// would not be detected by a shallow property comparison
this.state.colors.push('red');
this.state.colors.push("red");
// Force that particular state property to be considered dirty so
// that it will trigger the component's view to be updated
this.setStateDirty('colors');
this.setStateDirty("colors");
```
### `subscribeTo(emitter)`
| params | description |
| ------- | ----------- |
| `emitter` | a node.js [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) or a DOM object that emits events (`window`, `document`, etc.) |
| return value | a tracked subscription |
| params | description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `emitter` | a node.js [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) or a DOM object that emits events (`window`, `document`, etc.) |
| return value | a tracked subscription |
When a component is destroyed, it is necessary to remove any listeners that were attached by the component in order to prevent a memory leak. By using `subscribeTo`, Marko will automatically track and remove any listeners you attach when the component is destroyed.
When a component is destroyed, it is necessary to remove any listeners that were attached by the component in order to prevent a memory leak. By using `subscribeTo`, Marko will automatically track and remove any listeners you attach when the component is destroyed.
Marko uses [`listener-tracker`](https://github.com/patrick-steele-idem/listener-tracker) to provide this feature.
_example.js_
```js
this.subscribeTo(window).on('scroll', () => {
console.log('The user scrolled the window!');
this.subscribeTo(window).on("scroll", () => {
console.log("The user scrolled the window!");
});
```
@ -736,40 +739,40 @@ this.subscribeTo(window).on('scroll', () => {
Immediately, executes any pending updates to the DOM rather than following the normal queued update mechanism for rendering.
```js
this.setState('foo', 'bar');
this.setState("foo", "bar");
this.update(); // Force the DOM to update
this.setState('hello', 'world');
this.setState("hello", "world");
this.update(); // Force the DOM to update
```
## Event methods
A Marko component inherits from [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter). Below are a few commonly used methods, view the node docs for the full list.
A Marko component inherits from [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter). Below are a few commonly used methods, view the node docs for the full list.
### `emit(eventName, ...args)`
| params | type | description |
| ------- | ---- | ----------- |
| `eventName` | `String` | the name of the event |
| `...args` | `Any` | all subsequent parameters are passed to the listeners |
| params | type | description |
| ----------- | -------- | ----------------------------------------------------- |
| `eventName` | `String` | the name of the event |
| `...args` | `Any` | all subsequent parameters are passed to the listeners |
Emits a UI component custom event. If a UI component attached a listener with the matching `eventName` then the corresponding event listener method will be automatically invoked. Event listeners can be attached using either the [`on-[event](methodName, ...args)` attribute](#declarative-custom-events) attribute or `targetComponent.on()`.
### `on(eventName, handler)`
| params | type | description |
| ------- | ---- | ----------- |
| `eventName` | `String` | the name of the event to listen for |
| `handler` | `Function` | the function to call when the event is fired |
| params | type | description |
| ----------- | ---------- | -------------------------------------------- |
| `eventName` | `String` | the name of the event to listen for |
| `handler` | `Function` | the function to call when the event is fired |
Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.
### `once(eventName, handler)`
| params | type | description |
| ------- | ---- | ----------- |
| `eventName` | `String` | the name of the event to listen for |
| `handler` | `Function` | the function to call when the event is fired |
| params | type | description |
| ----------- | ---------- | -------------------------------------------- |
| `eventName` | `String` | the name of the event to listen for |
| `handler` | `Function` | the function to call when the event is fired |
Adds a one time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked.
@ -777,12 +780,12 @@ Adds a one time listener function for the event named eventName. The next time e
Marko defines six distinct lifecycle events:
- `create`
- `input`
- `render`
- `mount`
- `update`
- `destroy`
* `create`
* `input`
* `render`
* `mount`
* `update`
* `destroy`
These events are emitted at specific points over the lifecycle of a component as shown below:
@ -807,7 +810,7 @@ emit('render')  emit('update')
**Destroy**
```js
emit('destroy')
emit("destroy");
```
### Lifecycle event methods
@ -825,18 +828,19 @@ class {
}
```
> **ProTip:** When a lifecycle event occurs in the browser, the corresponding event is emitted on the component instance. A parent component, or other code that has access to the component instance, can listen for these events. For example:
> **ProTip:** When a lifecycle event occurs in the browser, the corresponding event is emitted on the component instance. A parent component, or other code that has access to the component instance, can listen for these events. For example:
```js
component.on('input', function(input, out) {
// The component was destroyed!
component.on("input", function(input, out) {
// The component was destroyed!
});
```
### `onCreate(input, out)`
| params | description |
| ------- | ----------- |
| `input` | the input data used to render the component for the first time |
| params | description |
| ------- | --------------------------------------------------------------- |
| `input` | the input data used to render the component for the first time |
| `out` | the async `out` used to render the component for the first time |
The `create` event is emitted (and `onCreate` is called) when the component is first created.
@ -844,6 +848,7 @@ The `create` event is emitted (and `onCreate` is called) when the component is f
`onCreate` is typically used to set the initial state for stateful components:
_example.marko_
```marko
class {
onCreate(input) {
@ -856,29 +861,30 @@ class {
### `onInput(input, out)`
| params | description |
| ------- | ----------- |
| params | description |
| ------- | ------------------ |
| `input` | the new input data |
The `input` event is emitted (and `onInput` is called) when the component receives input: both the initial input and for any subsequent updates to its input.
### `onRender(out)`
| params | description |
| ------- | ----------- |
| `out` | the async `out` for the current render |
| params | description |
| ------ | -------------------------------------- |
| `out` | the async `out` for the current render |
The `render` event is emitted (and `onRender` is called) when the component is about to be rendered (or re-rendered).
### `onMount()`
The `mount` event is emitted (and `onMount` is called) when the component is first mounted to the DOM. For a server-rendered component, this is the first event that is emitted in the browser.
The `mount` event is emitted (and `onMount` is called) when the component is first mounted to the DOM. For a server-rendered component, this is the first event that is emitted in the browser.
This is the first point at which `this.el` and `this.els` are defined. `onMount` is commonly used to attach third-party javascript (or `jQuery` if you're still doing that) plugins to the newly-mounted DOM element(s).
This is the first point at which `this.el` and `this.els` are defined. `onMount` is commonly used to attach third-party javascript (or `jQuery` if you're still doing that) plugins to the newly-mounted DOM element(s).
For example, attaching a library that monitors whether the component is in the viewport:
_example.marko_
```marko
import scrollmonitor from 'scrollmonitor';
@ -895,11 +901,11 @@ class {
### `onUpdate()`
The `update` event is emitted (and `onUpdate` is called) when the component is called after a component has re-rendered and the DOM has been updated. If a re-render does not cause the DOM to be updated (nothing changed), this event will not be fired.
The `update` event is emitted (and `onUpdate` is called) when the component is called after a component has re-rendered and the DOM has been updated. If a re-render does not cause the DOM to be updated (nothing changed), this event will not be fired.
### `onDestroy()`
The `destroy` event is emitted (and `onDestroy` is called) when the component is about to be unmounted from the DOM and cleaned up. `onDestroy` should be used to do any additional clean up beyond what Marko handles itself.
The `destroy` event is emitted (and `onDestroy` is called) when the component is about to be unmounted from the DOM and cleaned up. `onDestroy` should be used to do any additional clean up beyond what Marko handles itself.
For example, cleaning up from our `scrollmonitor` example in [`onMount`](#codeonmountcode):

View File

@ -1,14 +1,16 @@
# Concise syntax
Marko's concise syntax is very similar to the HTML syntax, except it's more... concise. Essentially, you take an HTML tag, remove the angle brackets (`<>`) and use indentation rather than a closing tag:
Marko's concise syntax is very similar to the HTML syntax, except it's more... concise. Essentially, you take an HTML tag, remove the angle brackets (`<>`) and use indentation rather than a closing tag:
_input.marko_
```marko
div class="thumbnail"
img src="https://example.com/thumb.png"
```
_output.html_
```html
<div class="thumbnail">
<img src="https://example.com/thumb.png"/>
@ -20,6 +22,7 @@ _output.html_
Marko provides a shorthand for declaring classes and ids on an element:
_input.marko_
```marko
div.my-class
span#my-id
@ -29,6 +32,7 @@ button#submit.primary.large
Yields this HTML:
_output.html_
```html
<div class="my-class"></div>
<span id="my-id"></span>
@ -39,11 +43,12 @@ _output.html_
## Text
Text in concise mode is denoted by two or more dashes (`--`).
Text in concise mode is denoted by two or more dashes (`--`).
If there is text on the same line following `--`, it is single-line text:
_single-line-text.marko_
```marko
-- Hello world
```
@ -51,6 +56,7 @@ _single-line-text.marko_
The dashes can also follow an element to give it a single text node as a child
_single-line-text.marko_
```marko
div -- Hello world
```
@ -58,6 +64,7 @@ div -- Hello world
If there is a line break immediately following `--`, everything following the `--` at the current indentation is parsed as multi-line line text.
_multi-line-text.marko_
```marko
div
--
@ -71,9 +78,10 @@ div
text
```
A multi-line text block can be ended by the same number of dashes that opened it. This allows it to have siblings:
A multi-line text block can be ended by the same number of dashes that opened it. This allows it to have siblings:
_multi-line-text.marko_
```marko
div
img src="https://example.com/photo.png"
@ -90,6 +98,7 @@ div
There is one "gotcha" that you need to be aware of. The Marko parser starts out in the concise mode. Therefore, given the following template:
_input.marko_
```marko
Hello World
Welcome to Marko
@ -98,6 +107,7 @@ Welcome to Marko
The output would be the following:
_output.html_
```html
<Hello World></Hello>
<Welcome to Marko></Welcome>
@ -106,7 +116,8 @@ _output.html_
Instead, prefix the lines with `--` so they are parsed as text:
_input.marko_
```marko
-- Hello World
-- Welcome to Marko
```
```

View File

@ -39,6 +39,7 @@ And support complex expressions:
### `<for>`
The `<for>` tag allows iterating over an array of items:
```marko
<ul>
<for(color in colors)>
@ -48,6 +49,7 @@ The `<for>` tag allows iterating over an array of items:
```
It may also be applied as an attribute:
```marko
<ul>
<li for(color in colors)>${color}</li>
@ -57,7 +59,7 @@ It may also be applied as an attribute:
With either of the above templates, and the following value for `colors`:
```js
var colors = ['red', 'green', 'blue'];
var colors = ["red", "green", "blue"];
```
The output HTML would be the following:
@ -167,7 +169,6 @@ The `from`, `to` and `step` values must be numerical expressions. If not specifi
#### Custom Iterator
```marko
static function reverseIterator(arrayList, callback) {
for(var i=arrayList.length-1; i>=0; i--){
@ -218,7 +219,6 @@ $ var n = 0;
### `body-only-if`
If you find that you have a wrapper element that is conditional, but whose body should always be rendered then you can use the `body-only-if` attribute to handle this use case. For example, to only render a wrapping `<a>` tag if there is a valid URL then you could do the following:
```marko
@ -246,9 +246,10 @@ Some body content
The following tags are always written using the [concise syntax](./concise.md), even when using HTML syntax for tags that generate HTML output.
### `import`
> **Static:** The code generated by `import` will run once when the template is loaded and be shared by all calls to render. It must be declared as a top level tag and does not have access to `data`, `state`, or other values passed in at render.
The `import` tag is used to access data and functions from external files. It follows the same syntax as the [JavaScript `import` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
The `import` tag is used to access data and functions from external files. It follows the same syntax as the [JavaScript `import` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
```marko
import sum from './utils/sum';
@ -308,9 +309,10 @@ import Hello from './components/hello/index.marko';
#### Layouts with nested attributes
In addition to including external content, you can inject additional content chunks into the external content. This is accomplished by using nested attribute tags which are denoted by the `@` symbol:
In addition to including external content, you can inject additional content chunks into the external content. This is accomplished by using nested attribute tags which are denoted by the `@` symbol:
_page.marko_
```marko
<include('./layout.marko')>
<@body>
@ -322,6 +324,7 @@ _page.marko_
Then in your layout template you can include the injected content:
_layout.marko_
```marko
<!doctype html>
<html>
@ -389,6 +392,7 @@ sample template shows how to use macro functions inside expressions:
The `<await>` tag is used to dynamically load in content from a data provider. The data provider can be a `Promise` or a `callback`. Once the provider returns it's results the children are rendered.
await-example.marko
```marko
$ var personPromise = new Promise((resolve, reject) => {
setTimeout(function() {
@ -404,27 +408,28 @@ $ var personPromise = new Promise((resolve, reject) => {
```
Advanced implementation:
+ `<await>` tag signature
* Basic usage: `<await(results from dataProvider)>...</await>`
* Optional attributes
- client-reorder `boolean`
- arg `expression`
- arg-* `string`
- method `string`
- timeout `integer`
- timeout-message `string`
- error-message `string`
- placeholder `string`
- renderTimeout `function`
- renderError `function`
- renderPlaceholder `function`
- name `string`
- scope `expression`
- show-after `string`
* Optional child tags
- `<await-placeholder>Loading...</await-placeholder>`
- `<await-timeout>Request timed out</await-timeout>`
- `<await-error>Request errored</await-error>`
* `<await>` tag signature
* Basic usage: `<await(results from dataProvider)>...</await>`
* Optional attributes
* client-reorder `boolean`
* arg `expression`
* arg-\* `string`
* method `string`
* timeout `integer`
* timeout-message `string`
* error-message `string`
* placeholder `string`
* renderTimeout `function`
* renderError `function`
* renderPlaceholder `function`
* name `string`
* scope `expression`
* show-after `string`
* Optional child tags
* `<await-placeholder>Loading...</await-placeholder>`
* `<await-timeout>Request timed out</await-timeout>`
* `<await-error>Request errored</await-error>`
## Comments
@ -442,7 +447,7 @@ Example comments:
/*
Block comments are also supported
*/
-- Hello
--Hello;
```
If you would like for your HTML comment to show up in the final output then you can use the custom `html-comment` tag.
@ -450,18 +455,21 @@ If you would like for your HTML comment to show up in the final output then you
### `<html-comment>`
_input.marko_
```marko
<html-comment>This is a comment that *will* be rendered</html-comment>
<h1>Hello</h1>
```
_output.html_
```html
<!--This is a comment that *will* be rendered-->
<h1>Hello</h1>
```
Alternatively, the `<marko-compiler-options>` tag may be used to configure comments for the entire template:
```marko
<marko-compiler-options preserve-comments/>
```
@ -479,6 +487,7 @@ Whitespace can be preserved using the `preserve-whitespace` attribute:
be preserved.
</div>
```
Alternatively, the `<marko-compiler-options>` tag may be used to configure whitespace for the entire template:
```marko
@ -488,11 +497,13 @@ Alternatively, the `<marko-compiler-options>` tag may be used to configure white
### `marko-body`
The `marko-body` attribute can be used to control how body content is parsed. The following values are supported:
- `html` - Body content will be parsed HTML (the default)
- `static-text` - Body content will be parsed as static text (HTML tags will be ignored). Placeholders will be ignored.
- `parsed-text` - Body content will be parsed as text (HTML tags will be ignored). Placeholders will not be ignored.
* `html` - Body content will be parsed HTML (the default)
* `static-text` - Body content will be parsed as static text (HTML tags will be ignored). Placeholders will be ignored.
* `parsed-text` - Body content will be parsed as text (HTML tags will be ignored). Placeholders will not be ignored.
_input.marko_
```marko
<div marko-body="static-text">
This is just one
@ -504,6 +515,7 @@ _input.marko_
```
_output.html_
```html
<div>
This is just one

View File

@ -10,7 +10,7 @@ To get started let's look at template-based tags which allow you to include anot
### Discovering tags
When compiling a template Marko will search starting at template's directory, up to the project root for directories named `components/`. It then attempts to load the children of these directories as custom tags. The children can be a Marko template or a directory with an `index.marko` template (and other supporting files).
When compiling a template Marko will search starting at template's directory, up to the project root for directories named `components/`. It then attempts to load the children of these directories as custom tags. The children can be a Marko template or a directory with an `index.marko` template (and other supporting files).
```dir
components/
@ -28,9 +28,9 @@ pages/
When compiling the template at `pages/home/index.marko`, the following tags would be found:
- `<app-header>`
- `<app-footer>`
- `<home-banner>`
* `<app-header>`
* `<app-footer>`
* `<home-banner>`
So now, instead of needing to specify a path:
@ -39,19 +39,20 @@ So now, instead of needing to specify a path:
```
You can just use the tag name:
```marko
<app-header/>
```
## Using tags from npm
Using custom tags from `npm` is easy. Ensure that the package is installed and listed in your `package.json` dependencies:
Using custom tags from `npm` is easy. Ensure that the package is installed and listed in your `package.json` dependencies:
```
npm install --save some-third-party-package
```
And that's it. Marko will now discover these tags when compiling your templates and you can simply use them in your templates:
And that's it. Marko will now discover these tags when compiling your templates and you can simply use them in your templates:
```marko
<div>
@ -67,52 +68,51 @@ As an example, given a template at path `/my-project/src/pages/login/template.ma
```json
{
"name": "my-package",
"dependencies": {
"foo": "1.0.0"
},
"devDependencies": {
"bar": "1.0.0"
}
"name": "my-package",
"dependencies": {
"foo": "1.0.0"
},
"devDependencies": {
"bar": "1.0.0"
}
}
```
The search path will be the following:
1. `/my-project/src/pages/login/marko.json`
2. `/my-project/src/pages/marko.json`
3. `/my-project/src/marko.json`
4. `/my-project/marko.json`
5. `/my-project/node_modules/foo/marko.json`
6. `/my-project/node_modules/bar/marko.json`
1. `/my-project/src/pages/login/marko.json`
2. `/my-project/src/pages/marko.json`
3. `/my-project/src/marko.json`
4. `/my-project/marko.json`
5. `/my-project/node_modules/foo/marko.json`
6. `/my-project/node_modules/bar/marko.json`
### Hiding taglibs
If you wish to hide particular folder and/or node_module from discovery of marko.json, you can exclude certain directories or packages. This is used primarily for testing.
If you wish to hide particular folder and/or node_module from discovery of marko.json, you can exclude certain directories or packages. This is used primarily for testing.
```javascript
require('marko/compiler').taglibFinder.excludeDir(dirPath);
// Where 'dirPath' is an absolute path to the folder containing marko.json
require("marko/compiler").taglibFinder.excludeDir(dirPath);
// Where 'dirPath' is an absolute path to the folder containing marko.json
require('marko/compiler').taglibFinder.excludePackage(packageName);
// Where 'packageName' is the name of the node_module containing marko.json
require("marko/compiler").taglibFinder.excludePackage(packageName);
// Where 'packageName' is the name of the node_module containing marko.json
```
These statements should be used before any rendering begins in the process.
### marko.json syntax
```json
{
"tags": {
"my-hello": {
"renderer": "./hello-renderer",
"attributes": {
"name": "string"
}
}
"tags": {
"my-hello": {
"renderer": "./hello-renderer",
"attributes": {
"name": "string"
}
}
}
}
```
@ -120,10 +120,10 @@ Marko also supports a short-hand for declaring tags and attributes. The followin
```json
{
"<my-hello>": {
"renderer": "./hello-renderer",
"@name": "string"
}
"<my-hello>": {
"renderer": "./hello-renderer",
"@name": "string"
}
}
```
@ -133,18 +133,18 @@ Tags can be defined by adding `"<tag_name>": <tag_def>` properties to your `mark
```json
{
"<my-hello>": {
"renderer": "./hello-renderer",
"@name": "string"
},
"<my-foo>": {
"renderer": "./foo-renderer",
"@*": "string"
},
"<my-bar>": "./path/to/my-bar/marko-tag.json",
"<my-baz>": {
"template": "./baz-template.marko"
},
"<my-hello>": {
"renderer": "./hello-renderer",
"@name": "string"
},
"<my-foo>": {
"renderer": "./foo-renderer",
"@*": "string"
},
"<my-bar>": "./path/to/my-bar/marko-tag.json",
"<my-baz>": {
"template": "./baz-template.marko"
}
}
```
@ -186,11 +186,11 @@ Above, we tell Marko this attribute name contains a pattern so that it will allo
### Custom directory scanning
You can configure the `tags-dir` value in your `marko.json` to configure the name of the directory that marko scans in for custom tags. As described above, by default it uses the name `components/`. You can override this at a directory level and give a path to another directory to scan:
You can configure the `tags-dir` value in your `marko.json` to configure the name of the directory that marko scans in for custom tags. As described above, by default it uses the name `components/`. You can override this at a directory level and give a path to another directory to scan:
```json
{
"tags-dir": "./ui-components"
"tags-dir": "./ui-components"
}
```
@ -198,7 +198,7 @@ You can configure the `tags-dir` value in your `marko.json` to configure the nam
```json
{
"tags-dir": ["./ui-components", "./components"]
"tags-dir": ["./ui-components", "./components"]
}
```

View File

@ -1,40 +1,39 @@
# Editor Plugins
## Atom
[Documentation](https://atom.io/packages/language-marko)
- Syntax highlighting
- Tag matching
- Tag and attribute autocompletion
- Code snippets
- Hyperclick (clickable tags and attributes)
- Prettyprinting ([marko-prettyprint](https://github.com/marko-js/marko-prettyprint) is used internally)
* Syntax highlighting
* Tag matching
* Tag and attribute autocompletion
* Code snippets
* Hyperclick (clickable tags and attributes)
* Prettyprinting ([marko-prettyprint](https://github.com/marko-js/marko-prettyprint) is used internally)
## Visual Studio Code
[Documentation](https://marketplace.visualstudio.com/items?itemName=pcanella.marko)
- Syntax highlighting
* Syntax highlighting
## Sublime
[Documentation](https://github.com/merwan7/sublime-marko)
- Syntax highlighting
* Syntax highlighting
## WebStorm
[Documentation](https://github.com/marko-js/marko-tmbundle)
- Syntax highlighting
* Syntax highlighting
## TextMate
[Documentation](https://github.com/marko-js/marko-tmbundle)
- Syntax highlighting
* Syntax highlighting
## CodeMirror

View File

@ -1,5 +1,4 @@
Express + Marko
=====================
# Express + Marko
See the [marko-express](https://github.com/marko-js-samples/marko-express) sample
project for a working example.
@ -13,32 +12,31 @@ npm install marko --save
## Skip the view engine
The built in view engine for express may be asynchronous, but it doesn't support streaming (check out [Rediscovering Progressive HTML Rendering](http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/) to see why this is so important). So instead we'll [bypass the view engine](https://strongloop.com/strongblog/bypassing-express-view-rendering-for-speed-and-modularity/).
The built in view engine for express may be asynchronous, but it doesn't support streaming (check out [Rediscovering Progressive HTML Rendering](http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/) to see why this is so important). So instead we'll [bypass the view engine](https://strongloop.com/strongblog/bypassing-express-view-rendering-for-speed-and-modularity/).
## Usage
Marko provides a submodule (`marko/express`) to add a `res.marko` method to the express response object. This function works much like `res.render`, but doesn't impose the restrictions of the express view engine and allows you to take full advantage of Marko's streaming and modular approach to templates.
Marko provides a submodule (`marko/express`) to add a `res.marko` method to the express response object. This function works much like `res.render`, but doesn't impose the restrictions of the express view engine and allows you to take full advantage of Marko's streaming and modular approach to templates.
By using `res.marko` you'll automatically have access to `req`, `res`, `app`, `app.locals`, and `res.locals` from within your Marko template and custom tags. These values are added to `out.global`.
By using `res.marko` you'll automatically have access to `req`, `res`, `app`, `app.locals`, and `res.locals` from within your Marko template and custom tags. These values are added to `out.global`.
```javascript
require("marko/node-require"); // Allow Node.js to require and load `.marko` files
require('marko/node-require'); // Allow Node.js to require and load `.marko` files
var express = require('express');
var markoExpress = require('marko/express');
var template = require('./template');
var express = require("express");
var markoExpress = require("marko/express");
var template = require("./template");
var app = express();
app.use(markoExpress()); //enable res.marko(template, data)
app.get('/', function(req, res) {
res.marko(template, {
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
});
app.get("/", function(req, res) {
res.marko(template, {
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
});
});
app.listen(8080);

View File

@ -14,24 +14,24 @@ npm install marko --save
## Usage
```js
const fastify = require('fastify')();
const fastify = require("fastify")();
fastify.register(require('point-of-view'), {
engine: {
marko: require('marko')
}
fastify.register(require("point-of-view"), {
engine: {
marko: require("marko")
}
});
fastify.get('/', (req, reply) => {
reply.view('/index.marko', {
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
});
fastify.get("/", (req, reply) => {
reply.view("/index.marko", {
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
});
});
fastify.listen(8080, err => {
if (err) throw err;
console.log(`Server listening on ${fastify.server.address().port}`);
if (err) throw err;
console.log(`Server listening on ${fastify.server.address().port}`);
});
```

View File

@ -7,6 +7,7 @@ The easiest way to get started with Marko is to use the [Try Online](https://mar
Marko makes it easy to represent your UI using a syntax that is like HTML:
_hello.marko_
```marko
<h1>Hello World</h1>
```
@ -14,6 +15,7 @@ _hello.marko_
In fact, Marko is so much like HTML, that you can use it as a replacement for a templating language like handlebars, mustache, or pug:
_template.marko_
```marko
<!doctype html>
<html>
@ -26,7 +28,7 @@ _template.marko_
</html>
```
However, Marko is much more than a templating language. It's a UI library that allows you to break your application into components that are self-contained and describe how the application view changes over time and in response to user actions.
However, Marko is much more than a templating language. It's a UI library that allows you to break your application into components that are self-contained and describe how the application view changes over time and in response to user actions.
In the browser, when the data representing your UI changes, Marko will automatically and efficiently update the DOM to reflect the changes.
@ -35,6 +37,7 @@ In the browser, when the data representing your UI changes, Marko will automatic
Let's say we have a `<button>` that we want to assign some behavior to when it is clicked:
_button.marko_
```marko
<button>Click me!</button>
```
@ -42,6 +45,7 @@ _button.marko_
Marko makes this really easy, allowing you to define a `class` for a component right in the `.marko` view and call methods of that class with `on-` attributes:
_button.marko_
```marko
class {
sayHi() {
@ -54,9 +58,10 @@ class {
### Adding state
Alerting when a button is clicked is great, but what about updating your UI in response to an action? Marko's stateful components make this easy. All you need to do is set `this.state` from inside your component's class. This makes a new `state` variable available to your view. When a value in `this.state` is changed, the view will automatically re-render and only update the part of the DOM that changed.
Alerting when a button is clicked is great, but what about updating your UI in response to an action? Marko's stateful components make this easy. All you need to do is set `this.state` from inside your component's class. This makes a new `state` variable available to your view. When a value in `this.state` is changed, the view will automatically re-render and only update the part of the DOM that changed.
_counter.marko_
```marko
class {
onCreate() {

View File

@ -13,13 +13,13 @@ npm install marko --save
## Usage
```javascript
'use strict';
"use strict";
require('marko/node-require');
require("marko/node-require");
const Hapi = require('hapi');
const Hapi = require("hapi");
const indexTemplate = require('./index');
const indexTemplate = require("./index");
const server = new Hapi.Server();
const port = 8080;
@ -27,22 +27,24 @@ const port = 8080;
server.connection({ port });
server.route({
method: 'GET',
path: '/',
handler (request, reply) {
return reply(indexTemplate.stream({
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
})).type('text/html');
}
method: "GET",
path: "/",
handler(request, reply) {
return reply(
indexTemplate.stream({
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
})
).type("text/html");
}
});
server.start((err) => {
if (err) {
throw err;
}
server.start(err => {
if (err) {
throw err;
}
console.log(`Server running on port: ${port}`);
console.log(`Server running on port: ${port}`);
});
```

View File

@ -12,23 +12,26 @@ npm install marko --save
## Usage
```js
require('marko/node-require').install();
require("marko/node-require").install();
const http = require('http');
const server = require('http').createServer();
const http = require("http");
const server = require("http").createServer();
const port = 8080;
const indexTemplate = require('./index.marko');
const indexTemplate = require("./index.marko");
server.on('request', (req, res) => {
indexTemplate.render({
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
}, res);
server.on("request", (req, res) => {
indexTemplate.render(
{
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
},
res
);
});
server.listen(port, () => {
console.log(`Successfully started server on port ${port}`);
console.log(`Successfully started server on port ${port}`);
});
```

View File

@ -10,14 +10,14 @@ project for a working example.
## Usage
```javascript
require('marko/node-require');
require("marko/node-require");
const Huncwot = require('huncwot');
const Huncwot = require("huncwot");
const app = new Huncwot();
const template = require('./index.marko');
const template = require("./index.marko");
app.get('/', request => template.stream({ name: 'Frank' }))
app.get("/", request => template.stream({ name: "Frank" }));
app.listen(3000);
```

View File

@ -2,7 +2,7 @@
## Trying out Marko
If you just want to play around with Marko in the browser, head on over to our [Try Online](https://markojs.com/try-online) feature. You'll be able to develop a Marko application right in your browser.
If you just want to play around with Marko in the browser, head on over to our [Try Online](https://markojs.com/try-online) feature. You'll be able to develop a Marko application right in your browser.
## Creating new apps
@ -37,6 +37,7 @@ yarn add marko
Let's say we have a simple view that we want to render in the browser: `hello.marko`
_hello.marko_
```marko
<h1>Hello ${input.name}</h1>
```
@ -44,16 +45,17 @@ _hello.marko_
First, let's create a `client.js` that requires the view and renders it to the body:
_client.js_
```js
var helloComponent = require('./hello');
helloComponent.renderSync({ name:'Marko' })
.appendTo(document.body);
```js
var helloComponent = require("./hello");
helloComponent.renderSync({ name: "Marko" }).appendTo(document.body);
```
We will also create a barebones HTML page to host our application:
_index.html_
```
<!doctype html>
<html>
@ -66,7 +68,7 @@ _index.html_
</html>
```
Now, we need to bundle these files for use in the browser. We can use a tool called [`lasso`](https://github.com/lasso-js/lasso) to do that for us, so let's get it (and the marko plugin) installed:
Now, we need to bundle these files for use in the browser. We can use a tool called [`lasso`](https://github.com/lasso-js/lasso) to do that for us, so let's get it (and the marko plugin) installed:
```
npm install --global lasso-cli
@ -79,7 +81,7 @@ Now we can build our bundle for the browser:
lasso --main client.js --plugins lasso-marko --inject-into index.html
```
This builds a `client.js` file to the newly created `static/` directory and injects the required `<script>` tags into our HTML page to load our application in the browser. If we had css in the view then `<link>` tags would have also been added.
This builds a `client.js` file to the newly created `static/` directory and injects the required `<script>` tags into our HTML page to load our application in the browser. If we had css in the view then `<link>` tags would have also been added.
Load up that page in your browser and you should see `Hello Marko` staring back at you.
@ -90,6 +92,7 @@ Load up that page in your browser and you should see `Hello Marko` staring back
Marko provides a custom Node.js require extension that allows you to `require` Marko views exactly like a standard JavaScript module. Take the following example `server.js`:
_hello.marko_
```marko
<div>
Hello ${input.name}!
@ -97,23 +100,23 @@ _hello.marko_
```
_server.js_
```js
// The following line installs the Node.js require extension
// for `.marko` files. This should be called once near the start
// of your application before requiring any `*.marko` files.
require('marko/node-require');
require("marko/node-require");
var fs = require('fs');
var fs = require("fs");
// Load a Marko view by requiring a .marko file:
var hello = require('./hello');
var out = fs.createWriteStream('hello.html', { encoding: 'utf8' });
hello.render({ name: 'Frank' }, out);
var hello = require("./hello");
var out = fs.createWriteStream("hello.html", { encoding: "utf8" });
hello.render({ name: "Frank" }, out);
```
Using the Node.js require extension is completely optional. If you prefer to not use the Node.js require extension then you will need to precompile all of the marko templates using [Marko CLI](https://github.com/marko-js/marko-cli):
```bash
marko compile hello.marko
```
@ -121,9 +124,10 @@ marko compile hello.marko
This will produce a `hello.marko.js` file next to the original template. The generated `.js` file will be what gets loaded by the Node.js runtime. It is important to leave off the `.marko` extension when requiring a Marko template so that the `.js` will be resolved correctly.
If you wish to only use the require extension in development, you can conditionally require it.
```js
if (!process.env.NODE_ENV) {
require('marko/node-require');
require("marko/node-require");
}
```
@ -132,26 +136,30 @@ if (!process.env.NODE_ENV) {
Let's update `server.js` to serve the view from an http server:
_server.js_
```js
// Allow requiring `.marko` files
require('marko/node-require');
require("marko/node-require");
var http = require('http');
var hello = require('./hello');
var http = require("http");
var hello = require("./hello");
var port = 8080;
http.createServer((req, res) => {
http
.createServer((req, res) => {
// let the browser know html is coming
res.setHeader('content-type', 'text/html');
res.setHeader("content-type", "text/html");
// render the output to the `res` output stream
hello.render({ name:'Marko' }, res);
}).listen(port);
hello.render({ name: "Marko" }, res);
})
.listen(port);
```
And give `hello.marko` some content:
_hello.marko_
```marko
<h1>Hello ${input.name}</h1>
```
@ -160,9 +168,9 @@ Start the server (`node server.js`) and open your browser to [http://localhost:8
#### Initializing server-rendered components
Marko automatically injects a list of components that need to be mounted in the browser, right before the closing `</body>` tag (as such, it required that you include a `<body>` in your rendered output).
Marko automatically injects a list of components that need to be mounted in the browser, right before the closing `</body>` tag (as such, it required that you include a `<body>` in your rendered output).
However, you still need to bundle the CSS & JavaScript for your page and include the proper `link`, `style`, and `script` tags. Luckily, the `lasso` taglib will do all the heavy lifting for you.
However, you still need to bundle the CSS & JavaScript for your page and include the proper `link`, `style`, and `script` tags. Luckily, the `lasso` taglib will do all the heavy lifting for you.
First install `lasso` and `lasso-marko`:
@ -173,6 +181,7 @@ npm install --save lasso lasso-marko
Next, in your page or layout view, add the `lasso-head` and `lasso-body` tags:
_layout.marko_
```marko
<!doctype>
<html>
@ -190,6 +199,7 @@ _layout.marko_
Finally, configure your server to serve the static files that `lasso` generates:
_server.js_
```js
app.use(require('lasso/middleware').serveStatic());
app.use(require("lasso/middleware").serveStatic());
```

View File

@ -11,20 +11,20 @@ project for a fully-working example.
## Usage
```javascript
require('marko/node-require');
require("marko/node-require");
const Koa = require('koa');
const Koa = require("koa");
const app = new Koa();
const template = require('./index.marko');
const template = require("./index.marko");
app.use((ctx, next) => {
ctx.type = 'html';
ctx.body = template.stream({
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
});
ctx.type = "html";
ctx.body = template.stream({
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
});
});
app.listen(8080);
@ -33,27 +33,27 @@ app.listen(8080);
You may also easily add `gzip` streaming support without additional dependencies:
```javascript
require('marko/node-require');
const { createGzip } = require('zlib');
require("marko/node-require");
const { createGzip } = require("zlib");
const Koa = require('koa');
const Koa = require("koa");
const app = new Koa();
const template = require('./index.marko');
const template = require("./index.marko");
app.use((ctx, next) => {
ctx.type = 'html';
ctx.body = template.stream({
name: 'Frank',
count: 30,
colors: ['red', 'green', 'blue']
});
ctx.type = "html";
ctx.body = template.stream({
name: "Frank",
count: 30,
colors: ["red", "green", "blue"]
});
ctx.vary('Accept-Encoding');
if (ctx.acceptsEncodings('gzip')) {
ctx.set('Content-Encoding', 'gzip');
ctx.body = ctx.body.pipe(createGzip());
}
ctx.vary("Accept-Encoding");
if (ctx.acceptsEncodings("gzip")) {
ctx.set("Content-Encoding", "gzip");
ctx.body = ctx.body.pipe(createGzip());
}
});
app.listen(8080);

View File

@ -56,12 +56,10 @@ After installing, the lasso custom tags can be used in your templates:
The `browser.json` provides a simple way for declaring _top-level_ page dependencies. For example:
_browser.json_
```json
{
"dependencies": [
"./style.css",
"require-run: ./client.js"
]
"dependencies": ["./style.css", "require-run: ./client.js"]
}
```
@ -72,10 +70,11 @@ Lasso.js will automatically bundle up transitive dependencies by building and wa
Marko templates can be imported and rendered by any JavaScript module. The code below shows how to render a top-level UI component and have it be mounted to the DOM as a child `document.body`:
_client.js_
```js
require('./components/app/index.marko')
.renderSync({})
.appendTo(document.body);
require("./components/app/index.marko")
.renderSync({})
.appendTo(document.body);
```
When Lasso.js bundles up the code above it will automatically bundle up the required `./components/app/index.marko` file.
@ -84,8 +83,8 @@ When Lasso.js bundles up the code above it will automatically bundle up the requ
If you are rendering the initial UI on the server then it is necessary to make sure that all UI components are bundled and sent to the browser so that UI components can be mounted in the browser. For example:
_about-me/index.marko_
```marko
<lasso-page package-path="./browser.json" />
@ -111,12 +110,10 @@ _about-me/index.marko_
Typically, adding the top-level UI component as a page dependency is all that is required:
_about-me/browser.json_
```json
{
"dependencies": [
"./style.css",
"require: ./components/app/index.marko"
]
"dependencies": ["./style.css", "require: ./components/app/index.marko"]
}
```

View File

@ -47,13 +47,14 @@ style.less {
You can easily `require`/`import` a single file component and interact with it using the exported JavaScript API:
```js
var clickCount = require('./src/components/click-count');
var clickCount = require("./src/components/click-count");
var component = clickCount.renderSync({
value: 10
})
.appendTo(document.body)
.getComponent();
var component = clickCount
.renderSync({
value: 10
})
.appendTo(document.body)
.getComponent();
component.increment();
```
@ -68,7 +69,7 @@ Of course, a single file component can also be embedded in another template as a
### Virtual DOM support ([#366](https://github.com/marko-js/marko/issues/366))
Because Marko renders raw HTML strings to a stream on the server, Marko has always been faster than other libraries by an [order of magnitude](https://github.com/patrick-steele-idem/marko-vs-react) when rendering on the server. However although Marko has been _pretty_ fast in the browser, it was a little behind some of our competitors. This was mainly because the output HTML string needed to be parsed into a DOM in order to do DOM diffing/patching.
Because Marko renders raw HTML strings to a stream on the server, Marko has always been faster than other libraries by an [order of magnitude](https://github.com/patrick-steele-idem/marko-vs-react) when rendering on the server. However although Marko has been _pretty_ fast in the browser, it was a little behind some of our competitors. This was mainly because the output HTML string needed to be parsed into a DOM in order to do DOM diffing/patching.
That's changed. Marko now supports multiple compilation outputs. Templates compiled for the server will continue to render to an HTML stream/string and templates compiled for the browser will now render to a fast and lightweight virtual DOM tree. The code samples below show how the two different compilation outputs compare:
@ -82,9 +83,7 @@ function render(data, out) {
out.w("<ul>");
marko_forEach(colors, function(color) {
out.w("<li class=\"color\">" +
marko_escapeXml(color) +
"</li>");
out.w('<li class="color">' + marko_escapeXml(color) + "</li>");
});
out.w("</ul>");
@ -98,10 +97,11 @@ _Compiled for VDOM output (browser-side):_
```javascript
var marko_attrs0 = {
"class": "color"
},
marko_node0 = marko_createElement("DIV", null, 1, marko_const_nextId())
.t("No colors!");
class: "color"
},
marko_node0 = marko_createElement("DIV", null, 1, marko_const_nextId()).t(
"No colors!"
);
function render(data, out) {
var colors = data.colors;
@ -110,8 +110,7 @@ function render(data, out) {
out.be("UL");
marko_forEach(colors, function(color) {
out.e("LI", marko_attrs0, 1)
.t(marko_str(color));
out.e("LI", marko_attrs0, 1).t(marko_str(color));
});
out.ee();
@ -123,28 +122,26 @@ function render(data, out) {
The VDOM output allows optimizations that were previously not possible:
- Static subtrees are pulled into variables that are only initialized once and reused for every render
- Static attributes that are on dynamic elements are pulled out to static variables
- Diffing is skipped when comparing static subtrees
- Diffing is skipped when comparing static attributes
* Static subtrees are pulled into variables that are only initialized once and reused for every render
* Static attributes that are on dynamic elements are pulled out to static variables
* Diffing is skipped when comparing static subtrees
* Diffing is skipped when comparing static attributes
Our benchmarks show a significant improvement in rendering time and we are consistently outperforming React/Preact/Inferno, Vue and other UI libraries.
### Merge in Marko Widgets ([#390](https://github.com/marko-js/marko/issues/390))
A big part of this release is a shift in focus from Marko being merely a templating language to a complete UI library. As such, we are providing first-class support for components.
A big part of this release is a shift in focus from Marko being merely a templating language to a complete UI library. As such, we are providing first-class support for components.
You will no longer need to install `marko-widgets` as an external library, and there is more cohesion between the templates and components/components.
### Improved component lifecycle methods ([#396](https://github.com/marko-js/marko/issues/396))
- `getInitialState()``onInput(input)`
- `getComponentConfig()``onInput(input)`
- `init(config)``onMount()`
- `getTemplateData(input, state)` ➔ (no longer needed)
- `getInitialProps(input)` ➔ (no longer needed)
* `getInitialState()``onInput(input)`
* `getComponentConfig()``onInput(input)`
* `init(config)``onMount()`
* `getTemplateData(input, state)` ➔ (no longer needed)
* `getInitialProps(input)` ➔ (no longer needed)
```js
class {
@ -190,7 +187,7 @@ class {
// Helper methods:
reset() {
this.state.count = this.initialCount;
this.state.count = this.initialCount;
}
increment() {
@ -213,7 +210,7 @@ class {
return {
count: input.count || 0
};
}
}
increment() {
this.setState('count', this.state.count+1);
}
@ -228,7 +225,7 @@ class {
this.state = {
count: input.count || 0
};
}
}
increment() {
this.state.count++;
}
@ -249,7 +246,7 @@ In addition, the default state can now be declared:
this.state = {
count: input.count
};
}
}
increment() {
this.state.count++;
@ -263,29 +260,29 @@ In Marko v3, UI components exported an API that included DOM insertion methods w
```js
// Append to an existing DOM node:
require('./template.marko')
.renderSync({ name: 'Frank '})
.appendTo(document.body);
require("./template.marko")
.renderSync({ name: "Frank " })
.appendTo(document.body);
// Replace an existing DOM node:
require('./template.marko')
.renderSync({ name: 'Frank '})
.replace(document.getElementById('foo'));
require("./template.marko")
.renderSync({ name: "Frank " })
.replace(document.getElementById("foo"));
```
### Less Boilerplate
- Removed: `w-bind`
- Removed: `w-extend`
- Removed: `require('marko-widgets').defineComponent(...)`
- Removed: `require('marko-widgets').defineWidget(...)`
- Removed: `require('marko-widgets').defineRenderer(...)`
- Removed: `w-body` (use `<include()>` instead)
- `w-on*="handleSomeEvent"` --> `on*('handleSomeEvent')`
- `w-id` --> `key`
- `w-for` --> `for-key`
- `w-preserve` --> `no-update`
- `class="foo" w-preserve-attrs="class"` --> `class:no-update="foo"`
* Removed: `w-bind`
* Removed: `w-extend`
* Removed: `require('marko-widgets').defineComponent(...)`
* Removed: `require('marko-widgets').defineWidget(...)`
* Removed: `require('marko-widgets').defineRenderer(...)`
* Removed: `w-body` (use `<include()>` instead)
* `w-on*="handleSomeEvent"` --> `on*('handleSomeEvent')`
* `w-id` --> `key`
* `w-for` --> `for-key`
* `w-preserve` --> `no-update`
* `class="foo" w-preserve-attrs="class"` --> `class:no-update="foo"`
Some of these things are described in more detail later in this document.
@ -317,8 +314,7 @@ $ {
}
```
JavaScript blocks can be embedded anywhere by putting `$ ` at the start of the line (ignoring whitespace) and it works the same for both the concise syntax and the non-concise syntax:
JavaScript blocks can be embedded anywhere by putting `$` at the start of the line (ignoring whitespace) and it works the same for both the concise syntax and the non-concise syntax:
```marko
<div.hello>
@ -384,9 +380,9 @@ static {
### Template variables
- `data` --> `input` - References the input object (should be treated as immutable)
- Introduced `state` - References the components raw state object (for components only)
- Introduced `component` - References the component instance (for components only)
* `data` --> `input` - References the input object (should be treated as immutable)
* Introduced `state` - References the components raw state object (for components only)
* Introduced `component` - References the component instance (for components only)
## Other Improvements
@ -399,6 +395,7 @@ In Marko v4, only the attributes rendered by Marko are ever modified by Marko. A
### Allow multiple top-level DOM elements to be bound ([#393](https://github.com/marko-js/marko/issues/393))
**Old:**
```marko
<div w-bind>
<h1>The current count is ${data.count}</h1>
@ -407,6 +404,7 @@ In Marko v4, only the attributes rendered by Marko are ever modified by Marko. A
```
**New:**
```marko
<h1>The current count is ${input.count}</h1>
<button onClick('incrementCount')>Increment Count</button>
@ -417,6 +415,7 @@ In Marko v4, only the attributes rendered by Marko are ever modified by Marko. A
**Old:**
`index.js`
```js
module.exports = require('marko-widgets').defineComponent({
template: require('./template.marko'),
@ -434,7 +433,6 @@ module.exports = require('marko-widgets').defineComponent({
**New:**
`component.js`
```js
@ -456,6 +454,7 @@ module.exports = {
### Allow event handler attribute to bind additional arguments ([#401](https://github.com/marko-js/marko/issues/401))
**Old:**
```marko
<ul for(color in colors)>
<li w-onClick="handleColorClick" data-color=color>${color}</li>
@ -469,6 +468,7 @@ handleColorClick(event, el) {
```
**New:**
```marko
class {
handleColorClick(color, event, el) {
@ -497,6 +497,7 @@ Marko v4 introduces ES6 style imports for importing other JavaScript modules:
```
**New:**
```marko
import helpers from "./helpers"
<div>Total: ${helpers.formatCurrency(data.total)}</div>
@ -509,19 +510,22 @@ import { formatCurrency } from "./helpers"
<div>Total: ${formatCurrency(data.total)}</div>
```
### Allow dynamic custom tags/components to be used with `<include>` ([#139](https://github.com/marko-js/marko/issues/139))
**Old:**
```marko
<invoke data.myComponent.renderer({name: 'Frank'}, out)/>
```
**New:**
```marko
<include(input.myComponent) name='Frank' />
```
or
```marko
<include(input.myComponent, {name: 'Frank'}) />
```
@ -597,7 +601,6 @@ components/hello/
},
...
}
```
`template.marko`
@ -638,11 +641,13 @@ $ var age = calculateAge(state.birthday);
### Make output of render `Promise`-compatible ([#251](https://github.com/marko-js/marko/issues/251))
**Old:**
```js
template.render({}, function(err, html, out) {});
```
**New:**
```js
template.render({})
.then(function(result){})
@ -658,6 +663,7 @@ NOTE: callback/events still work as well
### Make `<await-reorderer/>` optional ([#410](https://github.com/marko-js/marko/issues/410))
**Old:**
```marko
<html>
...
@ -669,6 +675,7 @@ NOTE: callback/events still work as well
```
**New:**
```marko
<html>
...
@ -677,21 +684,24 @@ NOTE: callback/events still work as well
</body>
</html>
```
*Automatically inserted before `</body>`*
_Automatically inserted before `</body>`_
### Allow multiple extensions when installing the Node.js require hook ([#407](https://github.com/marko-js/marko/issues/407))
**Old:**
```js
require('marko/node-require').install({
extension: '.marko'
require("marko/node-require").install({
extension: ".marko"
});
```
**New:**
```js
require('marko/node-require').install({
extensions: ['.marko', '.marko.xml', '.html']
require("marko/node-require").install({
extensions: [".marko", ".marko.xml", ".html"]
});
```
@ -702,12 +712,14 @@ Hot reload any extensions that were registered via `require('marko/node-require'
### Allow spaces around attributes ([#403](https://github.com/marko-js/marko/issues/403))
**Old:**
```marko
var className="foo"
<div class=className/>
```
**New:**
```marko
$ var className = "foo"
<div class = className/>
@ -718,30 +730,34 @@ $ var className = "foo"
### Allow compile-time transformers to be registered at the template level ([#408](https://github.com/marko-js/marko/issues/408))
`marko.json`
```json
{
"transformer": "./my-transformer.js"
"transformer": "./my-transformer.js"
}
```
`my-transformer.js`
```js
module.exports = function transform(rootNode, context) {
// ...
// ...
};
```
[see commit](https://github.com/marko-js/marko/commit/a35e6bdbc3fe6e7f4e92fb377c435e29ab3d6e33)
[see commit](https://github.com/marko-js/marko/commit/a35e6bdbc3fe6e7f4e92fb377c435e29ab3d6e33)
### Allow regular expression for an HTML attribute value ([#386](https://github.com/marko-js/marko/issues/386))
**Old:**
```marko
<!-- escaped backslash (\) since strings are parsed as JS values -->
<input type="text" pattern="\\w{2,20}" />
```
**New:**
```marko
<!-- just use a regex -->
<input type="text" pattern=/\w{2,20}/ />
@ -749,13 +765,14 @@ module.exports = function transform(rootNode, context) {
## Deprecations
A huge effort is being made to make this release as painless as possible and keep backwards compatibility wherever possible. It should be possible to continue to use custom tags that were developed against v3 with the v4 release as long as there are no dependencies on features deprecated in Marko v3 that have now been removed in Marko v4 (see [Breaking Changes](#breaking-changes) below).
A huge effort is being made to make this release as painless as possible and keep backwards compatibility wherever possible. It should be possible to continue to use custom tags that were developed against v3 with the v4 release as long as there are no dependencies on features deprecated in Marko v3 that have now been removed in Marko v4 (see [Breaking Changes](#breaking-changes) below).
Additionally, [`marko-migrate`](https://github.com/marko-js/marko-migrate) will be updated to handle many of the deprecations described below.
### Deprecate `<script marko-init>` and replace with `static` section ([#397](https://github.com/marko-js/marko/issues/397))
**Old:**
```marko
<script marko-init>
var format = require('format');
@ -765,6 +782,7 @@ Additionally, [`marko-migrate`](https://github.com/marko-js/marko-migrate) will
```
**New:**
```marko
static var format=require('format')
$ var name='World'
@ -778,6 +796,7 @@ Use embedded JavaScript blocks instead
### Deprecate `w-bind` ([#394](https://github.com/marko-js/marko/issues/394), [#395](https://github.com/marko-js/marko/issues/395))
**Old:**
```marko
<div w-bind>
...
@ -797,6 +816,7 @@ Use embedded JavaScript blocks instead
### Deprecate `widget-types` ([#514](https://github.com/marko-js/marko/issues/514))
**Old:**
```marko
<widget-types default="./component" mobile="./component-mobile"/>
@ -818,6 +838,7 @@ The `w-id` attribute was used to obtain references using `this.getEl(refId)`. `w
```
**New:**
```marko
<input type="text" key="nameInput" />
```
@ -832,6 +853,7 @@ Similarly, `w-for` has been been replaced with `for-key`:
```
**New:**
```marko
<label for-key="nameInput">Name</label>
<input type="text" key="nameInput" />
@ -842,19 +864,25 @@ Similarly, `w-for` has been been replaced with `for-key`:
### Deprecate `w-on*` in favor of `on*()` ([#420](https://github.com/marko-js/marko/issues/420))
**Old:**
```marko
<button w-on-click="handleClick">click me</button>
```
or
```marko
<button w-onClick="handleClick">click me</button>
```
**New:**
```marko
<button on-click('handleClick')>click me</button>
```
or
```marko
<button onClick('handleClick')>click me</button>
```
@ -862,6 +890,7 @@ or
### Deprecate `<init-widgets/>` ([#409](https://github.com/marko-js/marko/issues/409))
**Old:**
```marko
<html>
...
@ -923,6 +952,7 @@ Or, with an argument value:
### Deprecate `w-preserve*` and replace with `no-update*` ([#419](https://github.com/marko-js/marko/issues/419))
**Old:**
```marko
<div w-preserve>
...
@ -930,6 +960,7 @@ Or, with an argument value:
```
**New:**
```marko
<div no-update>
...
@ -939,6 +970,7 @@ Or, with an argument value:
### Deprecate `w-preserve-attrs` and replace with `:no-update` ([#422](https://github.com/marko-js/marko/issues/422))
**Old:**
```marko
<div style="color:#09c" w-preserve-attrs="style">
...
@ -946,6 +978,7 @@ Or, with an argument value:
```
**New:**
```marko
<div style:no-update="color:#09c">
...
@ -957,17 +990,21 @@ Or, with an argument value:
> `w-extend` is now deprecated
**Old:**
```marko
<div w-bind>
<some-component w-onEvent="handleEvent"/>
</div>
```
or
```marko
<some-component w-extend w-onEvent="handleEvent"/>
```
**New:**
```marko
<some-component onEvent('handleEvent')/>
```
@ -983,9 +1020,10 @@ In order to move forward it was necessary to introduce a few (minor) breaking ch
### Consistent rendering API ([#389](https://github.com/marko-js/marko/issues/389))
**Old:**
```js
var template = require('./template.marko');
var component = require('./my-component');
var template = require("./template.marko");
var component = require("./my-component");
var data = {};
template.render(data); // returns `out`
@ -998,9 +1036,10 @@ component.renderSync(data); // throws an error, not a method.
```
**New:**
```js
var template = require('./template.marko');
var component = require('./my-component');
var template = require("./template.marko");
var component = require("./my-component");
var data = {};
template.render(data); // returns `out`
@ -1038,11 +1077,13 @@ Given a template like this:
`include-target.marko` looks like:
**Old:**
```marko
-- Hello ${data['first-name']}
```
**New:**
```marko
-- Hello ${input.firstName}
```
@ -1052,6 +1093,7 @@ Given a template like this:
> Already deprecated in v3
**Old:**
```marko
<async-fragment var="foo" data-provider=data.provider>
${foo}
@ -1059,6 +1101,7 @@ Given a template like this:
```
**New:**
```marko
<await(foo from data.provider)>
${foo}
@ -1070,7 +1113,7 @@ Given a template like this:
> Already deprecated in v3
| Old | New |
|------------------------------|-----------------------|
| ---------------------------- | --------------------- |
| `asyncFragmentFinish` | `await:finish` |
| `asyncFragmentBegin` | `await:begin` |
| `asyncFragmentBeforeRender` | `await:beforeRender` |

View File

@ -42,10 +42,10 @@ class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }
this.state = { count: 0 };
function doIncrement(delta) {
this.setState((prevState) => ({
this.setState(prevState => ({
count: prevState.count + delta
}));
}
@ -55,25 +55,19 @@ class Counter extends React.Component {
}
render() {
var count = this.state.count;
var countClassName = 'count';
var countClassName = "count";
if (count > 0) {
countClassName += ' positive';
countClassName += " positive";
} else if (count < 0) {
countClassName += ' negative';
countClassName += " negative";
}
return (
<div className="click-count">
<div className={countClassName}>
{count}
</div>
<button onClick={this.decrement}>
-1
</button>
<button onClick={this.increment}>
+1
</button>
<div className={countClassName}>{count}</div>
<button onClick={this.decrement}>-1</button>
<button onClick={this.increment}>+1</button>
</div>
);
}
@ -145,113 +139,113 @@ At a high level here are some differences:
#### Differences in rendering
* **Improved performance:** Marko renders to a virtual DOM in the browser and
directly to an HTML stream on the server (Marko supports multiple compilation
targets).
directly to an HTML stream on the server (Marko supports multiple compilation
targets).
* **Improved performance:** Marko supports asynchronous rendering with [early
flushing of
HTML](http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/)
for improvements in actual and perceived page load times.
flushing of
HTML](http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/)
for improvements in actual and perceived page load times.
* **Improved performance: **React requires an additional client-side re-render if
a page is initially rendered on the server while Marko does not.
a page is initially rendered on the server while Marko does not.
* **Improved ease of use: **Marko automatically serializes UI component state and
input down to the browser so that the browser can pick up right where the server
left off.
input down to the browser so that the browser can pick up right where the server
left off.
* **Improved ease of use: **Marko is suitable for rendering an entire HTML page on
the server with support for tags such as `<doctype>` and `<html>`
the server with support for tags such as `<doctype>` and `<html>`
#### Differences in syntax
* **Improved ease of use: **Marko uses the
[HTML-JS](http://markojs.com/docs/syntax/) syntax and the
[JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) syntax is offered
for React.
[HTML-JS](http://markojs.com/docs/syntax/) syntax and the
[JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) syntax is offered
for React.
* **Improved ease of use: **Marko supports both a concise syntax and a familiar
HTML syntax.
HTML syntax.
* **Improved ease of use: **JSX requires strict XML while Marko aligns with less
strict HTML that web developers are used to.
* **Improved ease of use: **With Marko, *all* HTML attribute values are parsed as
JavaScript expressions.
strict HTML that web developers are used to.
* **Improved ease of use: **With Marko, _all_ HTML attribute values are parsed as
JavaScript expressions.
* **Improved ease of use: **Marko supports simple directives for conditionals,
looping, etc.
looping, etc.
* **JSX limitation: **JSX is “just JavaScript” but requires expressions that
preclude the usage of JavaScript statements such as in certain places.
preclude the usage of JavaScript statements such as in certain places.
#### Differences in compilation
* **Improved performance: **Marko supports multiple compilation outputs (Marko
VDOM and HTML streaming are currently supported).
VDOM and HTML streaming are currently supported).
* **Improved ease of use: **Marko compiles UI components to JavaScript modules
that export a rendering API.
that export a rendering API.
* **Expanded capabilities: **Marko supports a robust API for controlling how
custom tags and custom attributes get compiled and it supports compile-time
transforms based on a friendly Abstract Syntax Tree (AST).
custom tags and custom attributes get compiled and it supports compile-time
transforms based on a friendly Abstract Syntax Tree (AST).
* **Improved performance: **JSX is just syntactic sugar that translates elements
to `createElement()` function calls while the Marko compiler has full control over how things are
compiled and optimized.
to `createElement()` function calls while the Marko compiler has full control over how things are
compiled and optimized.
* **Improved ease of use: **React requires all UI components to be explicitly
imported before they can be used as custom tags while Marko supports both
explicit importing and implicit importing.
imported before they can be used as custom tags while Marko supports both
explicit importing and implicit importing.
* **Improved performance: **Marko has a modular runtime and the compiler generates
code that only imports the parts of the Marko runtime that are needed for much
smaller builds.
code that only imports the parts of the Marko runtime that are needed for much
smaller builds.
* **Improved ease of use: **Marko supports optional compile-time checks to ensure
that only allowed attributes are passed to custom tags. (React `PropTypes` only provide
validation at render-time)
* **Improved ease of use: **Marko validates *all* tag names at compile-time.
that only allowed attributes are passed to custom tags. (React `PropTypes` only provide
validation at render-time)
* **Improved ease of use: **Marko validates _all_ tag names at compile-time.
* **Improved ease of use: **Marko provides its own compiler that integrates with
Node.js and JavaScript module bundlers while React JSX requires babel and custom
babel transforms.
Node.js and JavaScript module bundlers while React JSX requires babel and custom
babel transforms.
#### Differences in UI components
* **Reduced boilerplate: **No explicit extending of JavaScript classes in Marko
(in contrast to `class Counter extends React.Component` in React).
(in contrast to `class Counter extends React.Component` in React).
* **Improved ease of use: **Modifications to UI component state are synchronous
with Marko while [the rules for React are more
complicated](https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous).
with Marko while [the rules for React are more
complicated](https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous).
* **Improved ease of use: **Marko watches UI component state objects to allow
state to be modified directly (e.g., `this.state.count++`).
state to be modified directly (e.g., `this.state.count++`).
* **Improved ease of use: **Marko supports single-file UI components combining
JavaScript behavior, CSS styling (with support for CSS preprocessors) and HTML
markup. (React requires using one of the many [CSS in JS
solutions](https://github.com/MicheleBertoli/css-in-js) if you want styles in
the same file as your component and there is no standard in the community)
JavaScript behavior, CSS styling (with support for CSS preprocessors) and HTML
markup. (React requires using one of the many [CSS in JS
solutions](https://github.com/MicheleBertoli/css-in-js) if you want styles in
the same file as your component and there is no standard in the community)
* **Improved maintainability: **Marko supports a seamless transition from a
single-file UI component to a multi-file UI component.
single-file UI component to a multi-file UI component.
* **Improved performance:** Marko assumes UI components are pure by default and
skips re-rendering when input properties and state are unchanged (React requires
extending
[React.PureComponent](https://facebook.github.io/react/docs/react-api.html#react.purecomponent)).
skips re-rendering when input properties and state are unchanged (React requires
extending
[React.PureComponent](https://facebook.github.io/react/docs/react-api.html#react.purecomponent)).
#### Differences in event systems
* **Reduced complexity: **React utilizes [synthetic
events](https://facebook.github.io/react/docs/events.html) while Marko utilizes
real DOM events.
events](https://facebook.github.io/react/docs/events.html) while Marko utilizes
real DOM events.
* **Improved ease of use: **Custom events are emitted using the [EventEmitter
API](https://nodejs.org/api/events.html) in Marko (e.g., `this.emit('myCustomEvent', arg1, arg2)`).
API](https://nodejs.org/api/events.html) in Marko (e.g., `this.emit('myCustomEvent', arg1, arg2)`).
* **Improved ease of use: **Marko has a consistent approach for listening to both
native DOM events and custom events.
native DOM events and custom events.
* **Improved ease of use: **React requires passing around `Function` references for custom
events while Marko automatically delegates emitted custom events to event
handler methods on components.
events while Marko automatically delegates emitted custom events to event
handler methods on components.
* **Improved ease of use: **Marko provides a simple mechanism for binding
additional arguments to event handler methods and `this` will be the component
instance.
additional arguments to event handler methods and `this` will be the component
instance.
#### Differences in compatibility
* **Marko limitation: **Marko has no support for native mobile similar to React
Native (although with Marko VDOM rendering, this is possible).
Native (although with Marko VDOM rendering, this is possible).
* **Marko limitation: **Marko requires a JavaScript module bundler (such as
[Lasso](http://markojs.com/docs/lasso/),
[Webpack](http://markojs.com/docs/webpack/),
[Rollup](http://markojs.com/docs/rollup/) or
[Browserify](http://markojs.com/docs/browserify/)) to be used in the browser
since Marko UI components compile down to JavaScript modules. (we consider using
a JavaScript module bundler a best practice)
[Lasso](http://markojs.com/docs/lasso/),
[Webpack](http://markojs.com/docs/webpack/),
[Rollup](http://markojs.com/docs/rollup/) or
[Browserify](http://markojs.com/docs/browserify/)) to be used in the browser
since Marko UI components compile down to JavaScript modules. (we consider using
a JavaScript module bundler a best practice)
*****
---
In the sections below we will take a closer look at some of the differences
between Marko and React.
@ -287,7 +281,7 @@ In React JSX, all attribute values are parsed as string values unless `{}` is us
#### Marko
With Marko, *all* attribute values are parsed as JavaScript expressions. The
With Marko, _all_ attribute values are parsed as JavaScript expressions. The
following Marko code is equivalent to the React JSX code above:
```marko
@ -309,10 +303,10 @@ React JSX starts with JavaScript and allows XML elements to be inlined as shown
below:
```jsx
import { formatDate } from './util';
import { formatDate } from "./util";
function formatName(person) {
return person.firstName + ' ' + person.lastName.charAt(0) + '.';
return person.firstName + " " + person.lastName.charAt(0) + ".";
}
export default function HelloMessage(props) {
@ -320,10 +314,8 @@ export default function HelloMessage(props) {
return (
<div>
Hello {formatName(person)}!
<span>
You were born on {formatDate(person.birthday)}.
</span>
Hello {formatName(person)}!
<span>You were born on {formatDate(person.birthday)}.</span>
</div>
);
}
@ -356,7 +348,7 @@ $ var person = input.person;
Lines prefixed with `$` are directly added to the compiled JavaScript output inside
the compiled `render()` function (for JavaScript code that should run for every render).
Lines prefixed with `static` are directly added to the compiled JavaScript output
outside the `render()` function (for code that should only run *once* when the template is
outside the `render()` function (for code that should only run _once_ when the template is
loaded).
### Syntax: HTML support
@ -370,7 +362,7 @@ documentation](https://facebook.github.io/react/docs/introducing-jsx.html#specif
> Since JSX is closer to JavaScript than HTML, React DOM uses `camelCase` property naming
> convention instead of HTML attribute names.
> For example, `class` becomes `className` in JSX, and `tabindex` becomes `tabIndex`.
> For example, `class` becomes `className` in JSX, and `tabindex` becomes `tabIndex`.
As a result of this caveat for React, [tools for converting HTML to JSX
exist](http://magic.reactjs.net/htmltojsx.htm).
@ -454,16 +446,19 @@ template:
```jsx
function renderColors(colors) {
return (
<ul>
{colors.map((color) => (
<li className="color" style={{
backgroundColor: color
}}>
{color}
</li>
))}
</ul>
);
<ul>
{colors.map(color => (
<li
className="color"
style={{
backgroundColor: color
}}
>
{color}
</li>
))}
</ul>
);
}
```
@ -619,8 +614,8 @@ Marko compiles component to JavaScript modules that export an API for rendering
the component as shown below:
```js
require('./components/greeting')
.renderSync({ name: 'Frank' })
require("./components/greeting")
.renderSync({ name: "Frank" })
.appendTo(document.body);
```
@ -628,8 +623,7 @@ The same UI component can be rendered to a stream such as a writable HTTP
response stream:
```js
require('./components/hello')
.render({ name: 'John' }, res);
require("./components/hello").render({ name: "John" }, res);
```
> The users of a Marko UI component do not need to know that the component was
@ -638,12 +632,11 @@ require('./components/hello')
Contrast this with React as an example:
```jsx
import ReactDOM from 'react-dom';
import ReactDOM from "react-dom";
ReactDOM.render(
<HelloMessage name="John" />,
document.getElementById('container')
document.getElementById("container")
);
```
@ -651,10 +644,9 @@ On top of that, React requires that a different module be imported to render the
exact same UI component on the server:
```jsx
import ReactDOMServer from 'react-dom/server';
import ReactDOMServer from "react-dom/server";
var html = ReactDOMServer.renderToString(
<HelloMessage name="John" />);
var html = ReactDOMServer.renderToString(<HelloMessage name="John" />);
```
### Custom tags
@ -664,8 +656,8 @@ var html = ReactDOMServer.renderToString(
With React, all custom tags for UI components must be explicitly imported:
```jsx
import Hello from './components/Hello';
import GoodBye from './components/GoodBye';
import Hello from "./components/Hello";
import GoodBye from "./components/GoodBye";
export default function HelloGoodBye(props) {
return (
@ -682,7 +674,7 @@ export default function HelloGoodBye(props) {
Marko supports a mechanism for [automatically discovering custom
tags](http://markojs.com/docs/custom-tags/#discovering-tags) for UI components
based on the project directory structure. Marko walks up the directory tree to
discover all directories and it will also automatically discover custom tags
discover all directories and it will also automatically discover custom tags
exported by installed packages. This approach negates the need for explicitly
importing a custom tag to reduce the amount of code needed in a Marko template.
For example given the following directory structure:
@ -695,7 +687,7 @@ For example given the following directory structure:
└── index.marko
```
The `<hello>` tag and the `<good-bye>` tag nested below the `components/`
The `<hello>` tag and the `<good-bye>` tag nested below the `components/`
directory will automatically be made available to the `index.marko` at the root:
```marko
@ -751,6 +743,7 @@ function render(input, out) {
```
#### Compiled for the browser:
```marko
var marko_template = require("marko/vdom").t(__filename);
@ -769,12 +762,13 @@ tags and it also provides support for compile-time transforms. While Babel
allows code transformations of JavaScript, the Marko compiler provides support
for resolving custom tags declaratively and the Marko AST provides for very
powerful and simple transformations as shown in the following code for rendering
Markdown to HTML at *compile-time*:
Markdown to HTML at _compile-time_:
**components/markdown/code-generator.js:**
```js
import marked from 'marked';
import {removeIndentation} from './util';
import marked from "marked";
import { removeIndentation } from "./util";
export default function generateCode(el, codegen) {
var bodyText = removeIndentation(el.bodyText);
@ -827,7 +821,7 @@ quickly jump to referenced files and methods, and [Pretty
printing](https://github.com/marko-js/atom-language-marko#prettyprint) to keep
your code readable.
*****
---
### Why Marko?
@ -837,20 +831,20 @@ Here are just a few reasons you should consider using
* Marko requires much less boilerplate.
* Marko has much better performance based on our benchmarks.
* Marko offers a clean and powerful syntax that aligns with HTML while also
allowing the full power of JavaScript.
allowing the full power of JavaScript.
* Marko has much less complexity and a very small runtime.
* Marko has a much lower page weight for faster page loads.
* Marko has strong integrations with Node.js.
* Marko allows for extremely powerful IDE and editor plugins (see the [Marko
plugin for Atom](https://github.com/marko-js/atom-language-marko) as an
example).
plugin for Atom](https://github.com/marko-js/atom-language-marko) as an
example).
* Marko has a powerful compiler that allows new features to be added without
introducing bloat.
introducing bloat.
* eBay relies heavily on Marko and it is being used to build ebay.com (including
the mobile web).
the mobile web).
* Marko has a strong and growing community on
[GitHub](https://github.com/marko-js/marko) and in
[Gitter](https://gitter.im/marko-js/marko).
[GitHub](https://github.com/marko-js/marko) and in
[Gitter](https://gitter.im/marko-js/marko).
Interested in learning more about Marko? If so, you can get additional
information on the [Marko website](http://markojs.com/). Join the conversation

View File

@ -12,11 +12,12 @@ npm install marko --save
## Usage
The partial code snippet below shows how a Marko UI component can be connected
The partial code snippet below shows how a Marko UI component can be connected
to a Redux store using the `store.subscribe()` method and the Marko `forceUpdate()`
method:
**counter.marko**
```javascript
import store from './store';
@ -41,25 +42,27 @@ class {
```
**reducer.js**
```js
module.exports = function (state, action) {
state = state || {
value: 0
};
// Additional reducer logic here...
return state;
module.exports = function(state, action) {
state = state || {
value: 0
};
// Additional reducer logic here...
return state;
};
```
In `counter.marko`, the imported store module exports a Redux store created
In `counter.marko`, the imported store module exports a Redux store created
using the following code:
**store.js**
```javascript
var redux = require('redux');
var counter = require('./reducer');
var redux = require("redux");
var counter = require("./reducer");
module.exports = redux.createStore(counter);
```

View File

@ -2,11 +2,11 @@
The files in this directory will be automatically generated by jsdoc.
- AST Nodes
- Runtime API
- Compiler API
- CompileContext API
- Builder API
- CodeGenerator API
- AsyncWriter API
- AsyncVDOMBuilder API
* AST Nodes
* Runtime API
* Compiler API
* CompileContext API
* Builder API
* CodeGenerator API
* AsyncWriter API
* AsyncVDOMBuilder API

View File

@ -3,18 +3,20 @@
To render a Marko view, you need to `require` it.
_example.js_
```js
var fancyButton = require('./components/fancy-button');
var fancyButton = require("./components/fancy-button");
```
> **Note:** If you are targeting node.js, you will need to enable the [require extension](./installing.md#require-marko-views) in order to require `.marko` files or you will need to precompile all of your templates using [Marko DevTools](https://github.com/marko-js/marko-devtools). If you are targeting the browser, you will need to use a bundler like [`lasso`](./lasso.md), [`webpack`](./webpack.md), [`browserify`](./browserify.md) or [`rollup`](./rollup.md).
> **Note:** If you are targeting node.js, you will need to enable the [require extension](./installing.md#require-marko-views) in order to require `.marko` files or you will need to precompile all of your templates using [Marko DevTools](https://github.com/marko-js/marko-devtools). If you are targeting the browser, you will need to use a bundler like [`lasso`](./lasso.md), [`webpack`](./webpack.md), [`browserify`](./browserify.md) or [`rollup`](./rollup.md).
Once you have a view, you can pass input data and render it:
_example.js_
```js
var button = require('./components/fancy-button');
var html = button.renderToString({ label:'Click me!' });
var button = require("./components/fancy-button");
var html = button.renderToString({ label: "Click me!" });
console.log(html);
```
@ -22,6 +24,7 @@ console.log(html);
The input data becomes available as `input` within a view, so if `fancy-button.marko` looked like this:
_./components/fancy-button.marko_
```marko
<button>${input.label}</button>
```
@ -34,21 +37,21 @@ The output HTML would be:
## Rendering methods
We used the `renderToString` method above to render the view, but there are a number of different method signatures that can be used to render.
We used the `renderToString` method above to render the view, but there are a number of different method signatures that can be used to render.
Many of these methods return a [`RenderResult`](#renderresult) which is an object with helper methods for working with the rendered output.
### `renderSync(input)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| return value | [`RenderResult`](#renderresult) | The result of the render |
| params | type | description |
| ------------ | ------------------------------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| return value | [`RenderResult`](#renderresult) | The result of the render |
Using `renderSync` forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
Using `renderSync` forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
var result = view.renderSync({});
result.appendTo(document.body);
@ -56,94 +59,93 @@ result.appendTo(document.body);
### `render(input)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
| params | type | description |
| ------------ | -------------------------------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
The `render` method returns an async `out` which is used to generate HTML on the server or a virtual DOM in the browser. In either case, the async `out` has a `then` method that follows the Promises/A+ spec, so it can be used as if it were a Promise. This promise resolves to a [`RenderResult`](#renderresult).
The `render` method returns an async `out` which is used to generate HTML on the server or a virtual DOM in the browser. In either case, the async `out` has a `then` method that follows the Promises/A+ spec, so it can be used as if it were a Promise. This promise resolves to a [`RenderResult`](#renderresult).
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
var resultPromise = view.render({});
resultPromise.then((result) => {
result.appendTo(document.body);
resultPromise.then(result => {
result.appendTo(document.body);
});
```
### `render(input, callback)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| `callback` | `Function` | a function to call when the render is complete |
| callback value | [`RenderResult`](#renderresult) | The result of the render |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
| params | type | description |
| -------------- | -------------------------------- | ---------------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| `callback` | `Function` | a function to call when the render is complete |
| callback value | [`RenderResult`](#renderresult) | The result of the render |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
view.render({}, (err, result) => {
result.appendTo(document.body);
result.appendTo(document.body);
});
```
### `render(input, stream)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| `stream` | `WritableStream` | a writeable stream |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
| params | type | description |
| ------------ | -------------------------------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| `stream` | `WritableStream` | a writeable stream |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
The HTML output is written to the passed `stream`.
```js
var http = require('http');
var view = require('./view'); // Import `./view.marko`
var http = require("http");
var view = require("./view"); // Import `./view.marko`
http.createServer((req, res) => {
res.setHeader('content-type', 'text/html');
view.render({}, res);
res.setHeader("content-type", "text/html");
view.render({}, res);
});
```
### `render(input, out)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| `out` | `AsyncStream`/`AsyncVDOMBuilder` | The async `out` to render to |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | The `out` that was passed |
| params | type | description |
| ------------ | -------------------------------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| `out` | `AsyncStream`/`AsyncVDOMBuilder` | The async `out` to render to |
| return value | `AsyncStream`/`AsyncVDOMBuilder` | The `out` that was passed |
The `render` method also allows passing an existing async `out`. If you do this, `render` will not automatically end the async `out` (this allows rendering a view in the middle of another view). If the async `out` won't be ended by other means, you are responsible for ending it.
The `render` method also allows passing an existing async `out`. If you do this, `render` will not automatically end the async `out` (this allows rendering a view in the middle of another view). If the async `out` won't be ended by other means, you are responsible for ending it.
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
var out = view.createOut();
view.render({}, out);
out.on('finish', () => {
console.log(out.getOutput());
out.on("finish", () => {
console.log(out.getOutput());
});
out.end();
```
### `renderToString(input)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| params | type | description |
| ------------ | -------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| return value | `String` | The HTML string produced by the render |
Returns an HTML string and forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
Returns an HTML string and forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
var html = view.renderToString({});
document.body.innerHTML = html;
@ -151,30 +153,30 @@ document.body.innerHTML = html;
### `renderToString(input, callback)`
| params | type | description |
| ------- | ---- | ----------- |
| `input` | `Object` | the input data used to render the view |
| callback value | `String` | The HTML string produced by the render |
| return value | `undefined` | N/A |
| params | type | description |
| -------------- | ----------- | -------------------------------------- |
| `input` | `Object` | the input data used to render the view |
| callback value | `String` | The HTML string produced by the render |
| return value | `undefined` | N/A |
An HTML string is passed to the callback.
```js
var view = require('./view'); // Import `./view.marko`
var view = require("./view"); // Import `./view.marko`
view.renderToString({}, (err, html) => {
document.body.innerHTML = html;
document.body.innerHTML = html;
});
```
### `stream(input)`
The `stream` method returns a node.js style stream of the output HTML. This method is available on the server, but is not available by default in the browser. If you need to use streams in the browser, you may `require('marko/stream')` as part of your client-side bundle.
The `stream` method returns a node.js style stream of the output HTML. This method is available on the server, but is not available by default in the browser. If you need to use streams in the browser, you may `require('marko/stream')` as part of your client-side bundle.
```js
var fs = require('fs');
var view = require('./view'); // Import `./view.marko`
var writeStream = fs.createWriteStream('output.html');
var fs = require("fs");
var view = require("./view"); // Import `./view.marko`
var writeStream = fs.createWriteStream("output.html");
view.stream({}).pipe(writeStream);
```
@ -182,25 +184,35 @@ view.stream({}).pipe(writeStream);
## RenderResult
### `getComponent()`
### `getComponents(selector)`
### `afterInsert(doc)`
### `getNode(doc)`
### `getOutput()`
### `appendTo(targetEl)`
### `insertAfter(targetEl)`
### `insertBefore(targetEl)`
### `prependTo(targetEl)`
### `replace(targetEl)`
### `replaceChildrenOf(targetEl)`
## Global data
If you need to make data available globally to all views that are rendered as the result of a call to one of the above render methods, you can pass the data as a `$global` property on the input data object. This data will be removed from `input` and merged into the `out.global` property.
If you need to make data available globally to all views that are rendered as the result of a call to one of the above render methods, you can pass the data as a `$global` property on the input data object. This data will be removed from `input` and merged into the `out.global` property.
```js
view.render({
$global: {
flags: ['mobile']
}
$global: {
flags: ["mobile"]
}
});
```

View File

@ -22,33 +22,33 @@ The following is the minimal recommend configuration to use Rollup with Marko:
_rollup.config.js_
```js
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import markoify from 'markoify';
import envify from 'envify';
import path from 'path';
import commonjsPlugin from "rollup-plugin-commonjs";
import browserifyPlugin from "rollup-plugin-browserify-transform";
import nodeResolvePlugin from "rollup-plugin-node-resolve";
import markoify from "markoify";
import envify from "envify";
import path from "path";
export default {
entry: path.join(__dirname, 'client.js'),
format: 'iife',
moduleName: 'app',
plugins: [
browserifyPlugin(markoify),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.marko' ]
}),
commonjsPlugin({
include: [ 'node_modules/**', '**/*.marko', '**/*.js'],
extensions: [ '.js', '.marko' ]
})
],
dest: path.join(__dirname, './dist/bundle.js')
entry: path.join(__dirname, "client.js"),
format: "iife",
moduleName: "app",
plugins: [
browserifyPlugin(markoify),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [".js", ".marko"]
}),
commonjsPlugin({
include: ["node_modules/**", "**/*.marko", "**/*.js"],
extensions: [".js", ".marko"]
})
],
dest: path.join(__dirname, "./dist/bundle.js")
};
```

View File

@ -3,30 +3,31 @@
Marko allows any Marko template/UI component to be rendered on the server or in the browser. A page can be rendered to a `Writable` stream such as an HTTP response stream as shown below:
```js
var template = require('./template'); // Import ./template.marko
var template = require("./template"); // Import ./template.marko
module.exports = function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
template.render({ name: 'Frank' }, res);
res.setHeader("Content-Type", "text/html; charset=utf-8");
template.render({ name: "Frank" }, res);
};
```
Marko can also provide you with a `Readable` stream.
```js
var template = require('./template'); // Import ./template.marko
var template = require("./template"); // Import ./template.marko
module.exports = function(req) {
// Return a Readable stream for someone to do something with:
return template.stream({ name: 'Frank' });
// Return a Readable stream for someone to do something with:
return template.stream({ name: "Frank" });
};
```
> **ProTip:** Marko also provides server-side framework integrations:
> - [express](/docs/express.md)
> - [hapi](/docs/hapi.md)
> - [koa](/docs/koa.md)
> - [huncwot](/docs/huncwot.md)
>
> * [express](/docs/express.md)
> * [hapi](/docs/hapi.md)
> * [koa](/docs/koa.md)
> * [huncwot](/docs/huncwot.md)
## UI Bootstrapping
@ -65,35 +66,37 @@ _routes/index/template.marko_
```
> **ProTip:** We have provided some sample apps to help you get started with Marko + Lasso
> - [marko-lasso](https://github.com/marko-js-samples/marko-lasso)
> - [ui-components-playground](https://github.com/marko-js-samples/ui-components-playground)
>
> * [marko-lasso](https://github.com/marko-js-samples/marko-lasso)
> * [ui-components-playground](https://github.com/marko-js-samples/ui-components-playground)
### Bootstrapping: Non-Lasso
If a JavaScript module bundler other than Lasso is being used then you will need to add some client-side code to bootstrap your application in the browser by doing the following:
If a JavaScript module bundler other than Lasso is being used then you will need to add some client-side code to bootstrap your application in the browser by doing the following:
1. Load/import/require all of the UI components that were rendered on the server (loading the top-level UI component is typically sufficient)
2. Call `require('marko/components').init()`
1. Load/import/require all of the UI components that were rendered on the server (loading the top-level UI component is typically sufficient)
2. Call `require('marko/components').init()`
For example, if `client.js` is the entry point for your client-side application:
_routes/index/client.js_
```js
// Load the top-level UI component:
require('./components/app/index');
require("./components/app/index");
// Now that all of the JavaScript modules for the UI component have been
// loaded and registered we can tell marko to bootstrap/initialize the app
// Initialize and mount all of the server-rendered UI components:
require('marko/components').init();
require("marko/components").init();
```
> **ProTip:** We have provided some sample apps to help you get started:
> - [marko-webpack](https://github.com/marko-js-samples/marko-webpack)
> - [marko-browserify](https://github.com/marko-js-samples/marko-browserify)
> - [marko-rollup](https://github.com/marko-js-samples/marko-rollup)
>
> * [marko-webpack](https://github.com/marko-js-samples/marko-webpack)
> * [marko-browserify](https://github.com/marko-js-samples/marko-browserify)
> * [marko-rollup](https://github.com/marko-js-samples/marko-rollup)
# Serialization
@ -119,23 +122,26 @@ class {
There are some caveats associated with rendering a page on the server:
- The UI component data for top-level UI components must be serializable:
- Only simple objects, numbers, strings, booleans, arrays and `Date` objects are serializable
- Functions are not serializable
- Care should be taken to avoid having Marko serialize too much data
- None of the data in `out.global` is serialized by default, but this can be changed as shown below
* The UI component data for top-level UI components must be serializable:
* Only simple objects, numbers, strings, booleans, arrays and `Date` objects are serializable
* Functions are not serializable
* Care should be taken to avoid having Marko serialize too much data
* None of the data in `out.global` is serialized by default, but this can be changed as shown below
## Serializing globals
If there are specific properties on the `out.global` object that need to be serialized then they must be whitelisted when the top-level page is rendered on the server. For example, to have the `out.global.apiKey` and the `out.global.locale` properties serialized you would do the following:
```js
template.render({
$global: {
serializedGlobals: {
apiKey: true,
locale: true
}
}
}, res);
template.render(
{
$global: {
serializedGlobals: {
apiKey: true,
locale: true
}
}
},
res
);
```

View File

@ -1,58 +1,52 @@
[{
"title": "Introduction",
"docs": [
"Installing",
"Getting started",
"Rendering",
"Syntax",
"Core tags",
"Custom tags",
"Components",
"Server-side rendering"
]
},{
"title": "Bundler Integrations",
"docs": [
"Lasso",
"Webpack",
"Browserify",
"Rollup"
]
},{
"title": "Server Integrations",
"docs": [
"Express",
"Koa",
"Hapi",
"HTTP",
{
"title": "Other",
"docs": [
"Fastify",
"Huncwot"
]
}
]
},{
"title": "Framework Integrations",
"docs": [
"Redux"
]
},{
"title": "Tooling",
"docs": [
"Editor plugins"
]
},{
"title": "Tutorials",
"docs": [
"Color Picker"
]
}, {
"title": "Articles",
"docs": [
"Marko vs React",
"Why is Marko Fast",
"10 Awesome Marko Features"
]
}]
[
{
"title": "Introduction",
"docs": [
"Installing",
"Getting started",
"Rendering",
"Syntax",
"Core tags",
"Custom tags",
"Components",
"Server-side rendering"
]
},
{
"title": "Bundler Integrations",
"docs": ["Lasso", "Webpack", "Browserify", "Rollup"]
},
{
"title": "Server Integrations",
"docs": [
"Express",
"Koa",
"Hapi",
"HTTP",
{
"title": "Other",
"docs": ["Fastify", "Huncwot"]
}
]
},
{
"title": "Framework Integrations",
"docs": ["Redux"]
},
{
"title": "Tooling",
"docs": ["Editor plugins"]
},
{
"title": "Tutorials",
"docs": ["Color Picker"]
},
{
"title": "Articles",
"docs": [
"Marko vs React",
"Why is Marko Fast",
"10 Awesome Marko Features"
]
}
]

View File

@ -1,12 +1,12 @@
# Syntax
Marko's syntax is based on HTML, so you basically already know it. Marko extends the HTML language to add a few nice features which we'll cover here.
Marko's syntax is based on HTML, so you basically already know it. Marko extends the HTML language to add a few nice features which we'll cover here.
> **ProTip:** Marko also supports a [beautiful concise syntax](./concise.md). If you'd prefer to see our documentation using this syntax, just click the `switch syntax` button in the corner of any Marko code sample.
## Text replacement
When you render a Marko template, you pass input data that is then available within the template as `input`. You can then use `${}` to insert a value into the template:
When you render a Marko template, you pass input data that is then available within the template as `input`. You can then use `${}` to insert a value into the template:
```marko
<div>
@ -22,7 +22,7 @@ You can actually pass any JavaScript expression here and the result of the expre
</div>
```
These values are automatically escaped so you don't accidentally insert malicious code. If you do need to pass unescaped HTML, you can use `$!{}`:
These values are automatically escaped so you don't accidentally insert malicious code. If you do need to pass unescaped HTML, you can use `$!{}`:
```marko
<div>
@ -42,7 +42,7 @@ If necessary, you can escape `$` using a backslash to have it be treated as text
## Root level text
Text at the root of a template (outside any tags) must be prefixed with the [concise syntax's `--`](./concise.md#text) to denote it is text. The parser starts in concise mode and would otherwise try to parse what you meant to be text as a concise tag declaration.
Text at the root of a template (outside any tags) must be prefixed with the [concise syntax's `--`](./concise.md#text) to denote it is text. The parser starts in concise mode and would otherwise try to parse what you meant to be text as a concise tag declaration.
```marko
-- Root level text
@ -75,22 +75,27 @@ _It does not contain any spaces_
```marko
<tag sum=1+2 difference=3-4/>
```
```marko
tag sum=1+2 difference=3-4
```
_Spaces are contained within matching `()`, `[]`, or `{}`_
```marko
<tag sum=(1 + 2) difference=(3 - 4)/>
```
```marko
tag sum=(1 + 2) difference=(3 - 4)
```
_Or, commas are used to delimit attributes_
```marko
<tag sum=1 + 2, difference=3 - 4/>
```
```marko
tag sum=1 + 2, difference=3 - 4
```
@ -104,6 +109,7 @@ Whitespace may optionally be used around the equal sign of an attribute:
```marko
<tag value = 5/>
```
```marko
tag value = 5
```
@ -129,14 +135,17 @@ With a value of `false` for `active`, the output would be the following:
```
### Dynamic attributes
You can use the `...attrs` syntax inside an open tag to merge in the properties of an object as attributes to a tag:
_index.js_
```js
template.render({ attrs:{ class:'active', href:'https://ebay.com/' } });
template.render({ attrs: { class: "active", href: "https://ebay.com/" } });
```
_link.marko_
```marko
<a ...input.attrs target="_blank">eBay</a>
```
@ -144,6 +153,7 @@ _link.marko_
would output the following HTML:
_output.html_
```html
<a class="active" href="https://ebay.com/" target="_blank">eBay</a>
```
@ -177,6 +187,7 @@ The `class` attribute also support object expressions or an array expressions (i
In both cases, the output will be the same:
_output.html_
```html
<div class="a c"></div>
```
@ -186,6 +197,7 @@ _output.html_
Marko provides a shorthand for declaring classes and ids on an element:
_source.marko_
```marko
<div.my-class/>
<span#my-id/>
@ -195,6 +207,7 @@ _source.marko_
Yields this HTML:
_output.html_
```html
<div class="my-class"></div>
<span id="my-id"></span>
@ -203,7 +216,7 @@ _output.html_
## Directives
Directives are denoted by parenthesis and take an argument instead of a value. Many directives may be used as both tags and attributes.
Directives are denoted by parenthesis and take an argument instead of a value. Many directives may be used as both tags and attributes.
```marko
<if(true)>
@ -226,11 +239,12 @@ Most directives support JavaScript expressions, and some even support multiple a
```
Others allow a custom syntax:
```marko
<for(item in items)/>
```
Directives are used by many of our [Core Tags](./core-tags.md) for control-flow (`<if>`, `<else-if>`, `<for>`, etc.) and other features. You can also use them in your own [Custom Tags](./custom-tags.md).
Directives are used by many of our [Core Tags](./core-tags.md) for control-flow (`<if>`, `<else-if>`, `<for>`, etc.) and other features. You can also use them in your own [Custom Tags](./custom-tags.md).
## Inline JavaScript
@ -249,7 +263,7 @@ $ var name = input.name;
</div>
```
A statement may continue onto subsequent lines if new lines are bounded by `{}`, `[]`, `()`, ``` `` ```, or `/**/`:
A statement may continue onto subsequent lines if new lines are bounded by `{}`, `[]`, `()`, ` `` `, or `/**/`:
```marko
$ var person = {
@ -270,6 +284,7 @@ $ {
```
### Static JavaScript
> **Static:** The JavaScript code that follows `static` will run once when the template is loaded and be shared by all calls to render. It must be declared at the top level and does not have access to values passed in at render.
Inline JavaScript will run each time your template is rendered, if you only want to initialize some values once, use the `static` keyword:

View File

@ -17,6 +17,7 @@ npm install marko-loader --save
Let's say we have a simple view that we want to render in the browser: `hello.marko`
_hello.marko_
```marko
<h1>Hello ${input.name}</h1>
```
@ -24,39 +25,42 @@ _hello.marko_
First, let's create a `client.js` that requires the view and renders it to the body:
_client.js_
```js
var myComponent = require('./hello.marko');
myComponent.renderSync({ name:'Marko' }).appendTo(document.body);
```js
var myComponent = require("./hello.marko");
myComponent.renderSync({ name: "Marko" }).appendTo(document.body);
```
Now, let's configure `webpack` to compile the `client.js` file and use `marko-loader` for any `*.marko` files:
_webpack.config.js_
```js
module.exports = {
entry: "./client.js",
output: {
path: __dirname,
filename: "static/bundle.js"
},
resolve: {
extensions: ['.js', '.marko']
},
module: {
loaders: [
{
test: /\.marko$/,
loader: 'marko-loader'
}
]
}
}
entry: "./client.js",
output: {
path: __dirname,
filename: "static/bundle.js"
},
resolve: {
extensions: [".js", ".marko"]
},
module: {
loaders: [
{
test: /\.marko$/,
loader: "marko-loader"
}
]
}
};
```
Run `webpack` from your terminal and you'll have a new `static/bundle.js` file created. Reference that from an html file and you're good to go.
Run `webpack` from your terminal and you'll have a new `static/bundle.js` file created. Reference that from an html file and you're good to go.
_index.html_
```html
<!doctype html>
<html>
@ -73,6 +77,7 @@ Load up that page in your browser and you should see `Hello Marko` staring back
If you're using inline css with pre-processors, you must configure the appropriate loader.
_pretty.marko_
```marko
style.less {
.pretty {
@ -84,18 +89,20 @@ style.less {
```
_webpack.config.js_
```js
//...
loaders: [
//...
{
test: /\.less$/, // matches style.less { ... } from our template
loader: "style-loader!css-loader!less-loader!"
},
//...
]
loaders: [
//...
{
test: /\.less$/, // matches style.less { ... } from our template
loader: "style-loader!css-loader!less-loader!"
}
//...
];
//...
```
## Extracting CSS
It is recommended to configure the [`ExtractTextPlugin`](https://www.npmjs.com/package/extract-text-webpack-plugin) so that you get a separate css bundle rather than it being included in the JavaScript bundle.
@ -105,37 +112,38 @@ npm install extract-text-webpack-plugin --save
```
_webpack.config.js_
```js
'use strict';
"use strict";
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: "./client.js",
output: {
path: __dirname,
filename: "static/bundle.js"
},
resolve: {
extensions: ['.js', '.marko']
},
module: {
loaders: [
{
test: /\.marko$/,
loader: 'marko-loader'
},
{
test: /\.(less|css)$/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader!less-loader"
})
}
]
},
plugins: [
// Write out CSS bundle to its own file:
new ExtractTextPlugin('static/bundle.css', { allChunks: true })
entry: "./client.js",
output: {
path: __dirname,
filename: "static/bundle.js"
},
resolve: {
extensions: [".js", ".marko"]
},
module: {
loaders: [
{
test: /\.marko$/,
loader: "marko-loader"
},
{
test: /\.(less|css)$/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader!less-loader"
})
}
]
},
plugins: [
// Write out CSS bundle to its own file:
new ExtractTextPlugin("static/bundle.css", { allChunks: true })
]
};
```

View File

@ -27,7 +27,7 @@ has switched to [a new methodology to measure and understand real-world
JavaScript
performance](https://v8project.blogspot.com/2016/12/how-v8-measures-real-world-performance.html).
Similarly, weve taken a look at how our developers are *actually* writing their
Similarly, weve taken a look at how our developers are _actually_ writing their
Marko components and have found patterns that could be further optimized.
Instead of focusing on benchmarks in this article, I want to focus on the
details of optimizations that we have applied to Marko.
@ -58,13 +58,11 @@ The compiled output is optimized for streaming HTML output on the server:
```js
var marko_template = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_escapeXml = marko_helpers.x;
marko_helpers = require("marko/runtime/html/helpers"),
marko_escapeXml = marko_helpers.x;
function render(input, out) {
out.w("<div>Hello " +
marko_escapeXml(input.name) +
"!</div>");
out.w("<div>Hello " + marko_escapeXml(input.name) + "!</div>");
}
```
@ -74,7 +72,8 @@ function render(input, out) {
var marko_template = require("marko/vdom").t(__filename);
function render(input, out) {
out.e("DIV", null, 3)
out
.e("DIV", null, 3)
.t("Hello ")
.t(input.name)
.t("!");
@ -104,12 +103,17 @@ the `styleAttr` helper is shown below:
var marko_styleAttr = require("marko/runtime/vdom/helper-styleAttr");
function render(input, out) {
var color = 'red';
out.e("DIV", {
var color = "red";
out.e(
"DIV",
{
style: marko_styleAttr({
backgroundColor: color
})
}, 0, 4);
backgroundColor: color
})
},
0,
4
);
}
```
@ -121,7 +125,7 @@ virtual DOM tree on the server its a two-step process to render HTML:
* First pass to produce an entire virtual DOM tree in memory
* Second pass to serialize the virtual DOM tree to an HTML string that can then be
sent over the wire (this requires traversing the entire tree structure)
sent over the wire (this requires traversing the entire tree structure)
In contrast, Marko renders directly to an HTML stream in a single pass. There is
no intermediate tree data structure.
@ -176,11 +180,12 @@ Marko will produce the following compiled output:
```js
var marko_attrs0 = {
"class": "hello"
};
class: "hello"
};
function render(input, out) {
out.e("DIV", marko_attrs0, 3)
out
.e("DIV", marko_attrs0, 3)
.t("Hello ")
.t(input.name)
.t("!");
@ -229,4 +234,4 @@ root, but this approach uses much less memory and reduces the amount of work
that is done during initialization. The extra overhead of delegating events to
components will not be noticeable so it is a very beneficial optimization.
*Cover image credit: [Superhero by Gan Khoon Lay from the Noun Project](https://thenounproject.com/search/?q=superhero&i=690775)*
_Cover image credit: [Superhero by Gan Khoon Lay from the Noun Project](https://thenounproject.com/search/?q=superhero&i=690775)_

7
env.js
View File

@ -3,12 +3,11 @@ var isDebug = false;
var env = process.env || {};
if (env.MARKO_DEBUG != null) {
isDebug = env.MARKO_DEBUG !== 'false' && env.MARKO_DEBUG !== '0';
isDebug = env.MARKO_DEBUG !== "false" && env.MARKO_DEBUG !== "0";
} else {
var NODE_ENV = env.NODE_ENV;
isDebug = NODE_ENV == null ||
NODE_ENV === 'development' ||
NODE_ENV === 'dev';
isDebug =
NODE_ENV == null || NODE_ENV === "development" || NODE_ENV === "dev";
}
exports.isDebug = isDebug;

View File

@ -1,6 +1,6 @@
var isDebug = require('./env').isDebug;
var target = isDebug ? 'marko/src/express' : 'marko/dist/express';
var isDebug = require("./env").isDebug;
var target = isDebug ? "marko/src/express" : "marko/dist/express";
module.exports = module.parent ?
module.parent.require(target) :
require(target);
module.exports = module.parent
? module.parent.require(target)
: require(target);

View File

@ -1,5 +1,4 @@
Marko Helpers
=============
# Marko Helpers
This directory contains helpers that were deprecated in Marko v3. Marko v4 no longer adds these helpers to every compiled template, but you can still import them if needed:

View File

@ -1,5 +1,5 @@
var notEmpty = require('./notEmpty');
var notEmpty = require("./notEmpty");
module.exports = function (o) {
module.exports = function(o) {
return !notEmpty(o);
};
};

View File

@ -3,9 +3,9 @@ module.exports = function notEmpty(o) {
return false;
} else if (Array.isArray(o)) {
return !!o.length;
} else if (o === '') {
} else if (o === "") {
return false;
}
return true;
};
};

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/hot-reload');
module.exports = require("./src/hot-reload");
} else {
module.exports = require('./dist/hot-reload');
module.exports = require("./dist/hot-reload");
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/');
module.exports = require("./src/");
} else {
module.exports = require('./dist/');
module.exports = require("./dist/");
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/components/legacy');
module.exports = require("./src/components/legacy");
} else {
module.exports = require('./dist/components/legacy');
module.exports = require("./dist/components/legacy");
}

View File

@ -1,7 +1,7 @@
var isDebug = require('./env').isDebug;
var isDebug = require("./env").isDebug;
if (isDebug) {
module.exports = require('./src/node-require');
module.exports = require("./src/node-require");
} else {
module.exports = require('./dist/node-require');
module.exports = require("./dist/node-require");
}

View File

@ -1,139 +1,155 @@
{
"name": "marko",
"description": "UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.",
"keywords": [
"front-end",
"templating",
"template",
"async",
"streaming",
"components",
"ui",
"vdom",
"dom",
"morphdom",
"virtual",
"virtual-dom"
],
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"scripts": {
"build": "node scripts/build.js",
"build-src": "node scripts/build.js src",
"prepublish": "npm run build-src",
"test": "npm run jshint -s && npm run mocha -s",
"test-ci": "npm run test-generate-coverage",
"mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js",
"test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && open ./coverage/index.html",
"test-generate-coverage": "nyc -asc npm test",
"jshint": "jshint src/ *.js",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>",
"Michael Rawlings <ml.rawlings@gmail.com>",
"Phillip Gates-Idem <phillip.idem@gmail.com>",
"Austin Kelleher <a@alk.im>",
"Dylan Piercey <pierceydylan@gmail.com>",
"Martin Aberer"
],
"dependencies": {
"app-module-path": "^2.2.0",
"argly": "^1.0.0",
"browser-refresh-client": "^1.0.0",
"char-props": "~0.1.5",
"complain": "^1.2.0",
"deresolve": "^1.1.2",
"escodegen": "^1.8.1",
"esprima": "^4.0.0",
"estraverse": "^4.2.0",
"events": "^1.0.2",
"events-light": "^1.0.0",
"he": "^1.1.0",
"htmljs-parser": "^2.3.2",
"lasso-caching-fs": "^1.0.1",
"lasso-modules-client": "^2.0.4",
"lasso-package-root": "^1.0.1",
"listener-tracker": "^2.0.0",
"minimatch": "^3.0.2",
"object-assign": "^4.1.0",
"property-handlers": "^1.0.0",
"raptor-json": "^1.0.1",
"raptor-polyfill": "^1.0.0",
"raptor-promises": "^1.0.1",
"raptor-regexp": "^1.0.0",
"raptor-util": "^3.2.0",
"resolve-from": "^2.0.0",
"shorthash": "0.0.2",
"simple-sha1": "^2.1.0",
"strip-json-comments": "^2.0.1",
"try-require": "^1.2.1",
"warp10": "^1.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-plugin-minprops": "^2.0.1",
"benchmark": "^2.1.1",
"bluebird": "^3.4.7",
"builtins": "^2.0.0",
"chai": "^3.3.0",
"codecov": "^2.3.0",
"coveralls": "^2.13.1",
"diffable-html": "^2.1.0",
"express": "^4.16.1",
"fs-extra": "^2.0.0",
"ignoring-watcher": "^1.0.2",
"istanbul-lib-instrument": "^1.8.0",
"it-fails": "^1.0.0",
"jquery": "^3.1.1",
"jsdom": "^9.12.0",
"jshint": "^2.5.0",
"lasso-resolve-from": "^1.2.0",
"marko-widgets": "^7.0.1",
"micromatch": "^3.0.4",
"mkdirp": "^0.5.1",
"mocha": "^5.0.1",
"nyc": "^10.3.2",
"open": "0.0.5",
"request": "^2.72.0",
"semver": "^5.4.1",
"shelljs": "^0.7.7",
"through": "^2.3.4",
"through2": "^2.0.1"
},
"license": "MIT",
"bin": {
"markoc": "bin/markoc"
},
"main": "index.js",
"browser": {
"./compiler.js": "./compiler-browser.marko",
"./components.js": "./components-browser.marko",
"./index.js": "./index-browser.marko",
"./legacy-components.js": "./legacy-components-browser.marko"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"nyc": {
"exclude": [
"**/benchmark/**",
"**/scripts/**",
"**/coverage/**",
"**/test/**",
"**/test-dist/**",
"**/test-generated/**",
"**/dist/**"
"name": "marko",
"version": "4.9.1",
"license": "MIT",
"description": "UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.",
"scripts": {
"build": "node scripts/build.js",
"build-src": "node scripts/build.js src",
"prepublish": "npm run build-src",
"precommit": "lint-staged",
"test": "npm run lint -s && npm run mocha -s",
"test-ci": "npm run check-format && npm run test-generate-coverage",
"mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js",
"test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && open ./coverage/index.html",
"test-generate-coverage": "nyc -asc npm test",
"lint": "eslint .",
"format": "prettier \"**/*.{js,json,css,md}\" --write",
"check-format": "prettier \"**/*.{js,json,css,md}\" -l",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov"
},
"lint-staged": {
"*.js": [
"eslint"
],
"*.{js,json,css,md}": [
"prettier --write",
"git add"
]
},
"dependencies": {
"app-module-path": "^2.2.0",
"argly": "^1.0.0",
"browser-refresh-client": "^1.0.0",
"char-props": "~0.1.5",
"complain": "^1.2.0",
"deresolve": "^1.1.2",
"escodegen": "^1.8.1",
"esprima": "^4.0.0",
"estraverse": "^4.2.0",
"events": "^1.0.2",
"events-light": "^1.0.0",
"he": "^1.1.0",
"htmljs-parser": "^2.3.2",
"lasso-caching-fs": "^1.0.1",
"lasso-modules-client": "^2.0.4",
"lasso-package-root": "^1.0.1",
"listener-tracker": "^2.0.0",
"minimatch": "^3.0.2",
"object-assign": "^4.1.0",
"property-handlers": "^1.0.0",
"raptor-json": "^1.0.1",
"raptor-polyfill": "^1.0.0",
"raptor-promises": "^1.0.1",
"raptor-regexp": "^1.0.0",
"raptor-util": "^3.2.0",
"resolve-from": "^2.0.0",
"shorthash": "0.0.2",
"simple-sha1": "^2.1.0",
"strip-json-comments": "^2.0.1",
"try-require": "^1.2.1",
"warp10": "^1.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-plugin-minprops": "^2.0.1",
"benchmark": "^2.1.1",
"bluebird": "^3.4.7",
"builtins": "^2.0.0",
"chai": "^3.3.0",
"codecov": "^2.3.0",
"coveralls": "^2.13.1",
"diffable-html": "^2.1.0",
"eslint": "^4.11.0",
"eslint-config-prettier": "^2.9.0",
"express": "^4.16.1",
"fs-extra": "^2.0.0",
"husky": "^0.14.3",
"ignoring-watcher": "^1.0.2",
"istanbul-lib-instrument": "^1.8.0",
"it-fails": "^1.0.0",
"jquery": "^3.1.1",
"jsdom": "^9.12.0",
"lasso-resolve-from": "^1.2.0",
"lint-staged": "^7.0.0",
"marko-widgets": "^7.0.1",
"micromatch": "^3.0.4",
"mkdirp": "^0.5.1",
"mocha": "^5.0.1",
"nyc": "^10.3.2",
"open": "0.0.5",
"prettier": "^1.11.1",
"request": "^2.72.0",
"semver": "^5.4.1",
"shelljs": "^0.7.7",
"through": "^2.3.4",
"through2": "^2.0.1"
},
"main": "index.js",
"browser": {
"./compiler.js": "./compiler-browser.marko",
"./components.js": "./components-browser.marko",
"./index.js": "./index-browser.marko",
"./legacy-components.js": "./legacy-components-browser.marko"
},
"bin": {
"markoc": "bin/markoc"
},
"nyc": {
"exclude": [
"**/benchmark/**",
"**/scripts/**",
"**/coverage/**",
"**/test/**",
"**/test-dist/**",
"**/test-generated/**",
"**/dist/**"
]
},
"homepage": "http://markojs.com/",
"logo": {
"url": "https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-small.png"
},
"repository": {
"type": "git",
"url": "https://github.com/marko-js/marko.git"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>",
"Michael Rawlings <ml.rawlings@gmail.com>",
"Phillip Gates-Idem <phillip.idem@gmail.com>",
"Austin Kelleher <a@alk.im>",
"Dylan Piercey <pierceydylan@gmail.com>",
"Martin Aberer"
],
"keywords": [
"front-end",
"templating",
"template",
"async",
"streaming",
"components",
"ui",
"vdom",
"dom",
"morphdom",
"virtual",
"virtual-dom"
]
},
"homepage": "http://markojs.com/",
"version": "4.9.1",
"logo": {
"url": "https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-small.png"
}
}

View File

@ -2,61 +2,73 @@
* Babel plugin for production builds which converts "MARKO_DEBUG"
* strings to false to be removed by babel-plugin-minify-dead-code-elimination.
*/
module.exports = function babelPluginMarkoDebug (babel) {
const t = babel.types;
return {
visitor: {
IfStatement: (path) => {
const node = path.node;
return replaceMarkoDebug(path, node.test, node.consequent, node.alternate);
},
ConditionalExpression: (path) => {
const node = path.node;
return replaceMarkoDebug(path, node.test, node.consequent, node.alternate);
},
LogicalExpression: (path) => {
const node = path.node;
module.exports = function babelPluginMarkoDebug() {
return {
visitor: {
IfStatement: path => {
const node = path.node;
return replaceMarkoDebug(
path,
node.test,
node.consequent,
node.alternate
);
},
ConditionalExpression: path => {
const node = path.node;
return replaceMarkoDebug(
path,
node.test,
node.consequent,
node.alternate
);
},
LogicalExpression: path => {
const node = path.node;
if (node.operator === '&&') {
return replaceMarkoDebug(path, node.left, node.right);
} else {
return replaceMarkoDebug(path, node.left, null, node.right);
if (node.operator === "&&") {
return replaceMarkoDebug(path, node.left, node.right);
} else {
return replaceMarkoDebug(path, node.left, null, node.right);
}
}
}
}
}
}
}
};
};
/**
* Replaces any conditions containing "MARKO_DEBUG" or !"MARKO_DEBUG" and collapses them.
*/
function replaceMarkoDebug (path, test, consequent, alternate) {
// Reverse replacement when negating the expression.
if (test.type === 'UnaryExpression' && test.operator === '!') {
const temp = consequent;
consequent = alternate;
alternate = temp;
test = test.argument;
}
// Only look for "MARKO_DEBUG" strings.
if (test.type !== 'StringLiteral') {
return;
}
// If we found a condition that is a string (and isn't "MARKO_DEBUG") it's most likely a mistake.
if (test.value !== 'MARKO_DEBUG') {
throw new Error(`Found if statement with StringLiteral "${test.value}", did you mean "MARKO_DEBUG".`);
}
if (alternate) {
if (alternate.type === 'BlockStatement') {
path.replaceWithMultiple(alternate.body);
} else {
path.replaceWith(alternate);
function replaceMarkoDebug(path, test, consequent, alternate) {
// Reverse replacement when negating the expression.
if (test.type === "UnaryExpression" && test.operator === "!") {
const temp = consequent;
consequent = alternate;
alternate = temp;
test = test.argument;
}
} else {
path.remove();
}
}
// Only look for "MARKO_DEBUG" strings.
if (test.type !== "StringLiteral") {
return;
}
// If we found a condition that is a string (and isn't "MARKO_DEBUG") it's most likely a mistake.
if (test.value !== "MARKO_DEBUG") {
throw new Error(
`Found if statement with StringLiteral "${
test.value
}", did you mean "MARKO_DEBUG".`
);
}
if (alternate) {
if (alternate.type === "BlockStatement") {
path.replaceWithMultiple(alternate.body);
} else {
path.replaceWith(alternate);
}
} else {
path.remove();
}
}

View File

@ -1,59 +1,55 @@
'use strict';
const fs = require('fs');
const path = require('path');
const buildDir = require('./util').buildDir;
"use strict";
const fs = require("fs");
const path = require("path");
const buildDir = require("./util").buildDir;
const babelOptions = {
"plugins": [
plugins: [
[
"minprops", {
"matchPrefix": "___",
"prefix": "",
"suffix": "_",
"hello": "world",
"context": "marko"
"minprops",
{
matchPrefix: "___",
prefix: "",
suffix: "_",
hello: "world",
context: "marko"
}
],
require.resolve('./babel-plugin-marko-debug')
require.resolve("./babel-plugin-marko-debug")
]
};
var target = process.argv[2];
var shouldBuildSrc = true;
var shouldBuildTest = true;
if (target === 'src') {
if (target === "src") {
shouldBuildTest = false;
}
if (shouldBuildSrc) {
buildDir('src', 'dist', {
babelExclude: [
'/taglibs/async/client-reorder-runtime.min.js'
],
buildDir("src", "dist", {
babelExclude: ["/taglibs/async/client-reorder-runtime.min.js"],
babelOptions
});
}
fs.writeFileSync(
path.join(__dirname, '../dist/build.json'),
path.join(__dirname, "../dist/build.json"),
JSON.stringify({ isDebug: false }, null, 4),
{ encoding: 'utf8' });
{ encoding: "utf8" }
);
if (shouldBuildTest) {
buildDir('test', 'test-dist', {
babelExclude: [
'*expected*.*',
'input.js*'
],
buildDir("test", "test-dist", {
babelExclude: ["*expected*.*", "input.js*"],
exclude: [
'/generated',
'*.marko.js',
'*.skip',
'*.generated.*',
'*actual*.*',
'actualized-expected.html*'
"/generated",
"*.marko.js",
"*.skip",
"*.generated.*",
"*actual*.*",
"actualized-expected.html*"
],
babelOptions
});

View File

@ -1,30 +1,32 @@
const shelljs = require('shelljs');
/* eslint-disable no-console */
const shelljs = require("shelljs");
const mkdir = shelljs.mkdir;
const rm = shelljs.rm;
const cp = shelljs.cp;
const path = require('path');
const fs = require('fs');
const path = require("path");
const fs = require("fs");
const babel = require("babel-core");
const mm = require('micromatch');
const mm = require("micromatch");
const rootDir = path.join(__dirname, '..');
const rootDir = path.join(__dirname, "..");
function babelTransformFile(sourceFile, targetFile, babelOptions) {
babelOptions = Object.assign({}, babelOptions);
babelOptions.filename = sourceFile;
var source = fs.readFileSync(sourceFile, 'utf-8');
var source = fs.readFileSync(sourceFile, "utf-8");
var transformed = babel.transform(source, babelOptions).code;
fs.writeFileSync(targetFile, transformed, { encoding: 'utf8' });
fs.writeFileSync(targetFile, transformed, { encoding: "utf8" });
}
function createMatcher(patterns) {
var matchers = patterns.map((pattern) => {
var matchers = patterns.map(pattern => {
return mm.matcher(pattern, { matchBase: true });
});
return function isMatch(file) {
for (var i=0; i<matchers.length; i++) {
for (var i = 0; i < matchers.length; i++) {
if (matchers[i](file)) {
return true;
}
@ -37,7 +39,7 @@ function createMatcher(patterns) {
function findFiles(dir, callback, isExcluded) {
function findFilesHelper(parentDir, parentRelativePath) {
var names = fs.readdirSync(parentDir);
for (var i=0; i<names.length; i++) {
for (var i = 0; i < names.length; i++) {
var name = names[i];
var file = path.join(parentDir, name);
var relativePath = path.join(parentRelativePath, name);
@ -56,7 +58,7 @@ function findFiles(dir, callback, isExcluded) {
}
}
findFilesHelper(dir, '/');
findFilesHelper(dir, "/");
}
exports.buildDir = function buildDir(sourceName, targetName, options) {
@ -78,24 +80,27 @@ exports.buildDir = function buildDir(sourceName, targetName, options) {
isBabelExcluded = createMatcher(options.babelExclude);
}
rm('-rf', distDir);
rm("-rf", distDir);
findFiles(sourceDir, function(sourceFile, relativePath, stat) {
var targetFile = path.join(distDir, relativePath);
var targetDir = path.dirname(targetFile);
findFiles(
sourceDir,
function(sourceFile, relativePath, stat) {
var targetFile = path.join(distDir, relativePath);
var targetDir = path.dirname(targetFile);
if (stat.isFile()) {
mkdir('-p', targetDir);
if (stat.isFile()) {
mkdir("-p", targetDir);
var ext = path.extname(relativePath);
if (ext !== '.js' || isBabelExcluded(relativePath)) {
console.log('Copying file:', relativePath);
cp(sourceFile, targetDir + '/');
} else {
console.log("Running babel: " + relativePath);
babelTransformFile(sourceFile, targetFile, babelOptions);
var ext = path.extname(relativePath);
if (ext !== ".js" || isBabelExcluded(relativePath)) {
console.log("Copying file:", relativePath);
cp(sourceFile, targetDir + "/");
} else {
console.log("Running babel: " + relativePath);
babelTransformFile(sourceFile, targetFile, babelOptions);
}
}
}
}, isExcluded);
},
isExcluded
);
};

5
src/.eslintrc Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"browser": true
}
}

View File

@ -1,5 +1,5 @@
var enabled = false;
var browserRefreshClient = require('browser-refresh-client');
var browserRefreshClient = require("browser-refresh-client");
exports.enable = function(options) {
if (!browserRefreshClient.isBrowserRefreshEnabled()) {
@ -16,13 +16,13 @@ exports.enable = function(options) {
// We set an environment variable so that _all_ marko modules
// installed in the project will have browser refresh enabled.
process.env.MARKO_BROWSER_REFRESH = 'true';
process.env.MARKO_BROWSER_REFRESH = "true";
var hotReload = require('./hot-reload');
var hotReload = require("./hot-reload");
hotReload.enable(options);
browserRefreshClient
.enableSpecialReload('*.marko marko.json marko-tag.json')
.enableSpecialReload("*.marko marko.json marko-tag.json")
.onFileModified(function(path) {
hotReload.handleFileModified(path, options);
});

View File

@ -1,85 +1,86 @@
'use strict';
"use strict";
var isArray = Array.isArray;
var ok = require('assert').ok;
var ok = require("assert").ok;
var Node = require('./ast/Node');
var Program = require('./ast/Program');
var TemplateRoot = require('./ast/TemplateRoot');
var FunctionDeclaration = require('./ast/FunctionDeclaration');
var FunctionCall = require('./ast/FunctionCall');
var Literal = require('./ast/Literal');
var Identifier = require('./ast/Identifier');
var Comment = require('./ast/Comment');
var If = require('./ast/If');
var ElseIf = require('./ast/ElseIf');
var Else = require('./ast/Else');
var Assignment = require('./ast/Assignment');
var BinaryExpression = require('./ast/BinaryExpression');
var LogicalExpression = require('./ast/LogicalExpression');
var Vars = require('./ast/Vars');
var Return = require('./ast/Return');
var HtmlElement = require('./ast/HtmlElement');
var Html = require('./ast/Html');
var Text = require('./ast/Text');
var ForEach = require('./ast/ForEach');
var ForEachProp = require('./ast/ForEachProp');
var ForRange = require('./ast/ForRange');
var HtmlComment = require('./ast/HtmlComment');
var SelfInvokingFunction = require('./ast/SelfInvokingFunction');
var ForStatement = require('./ast/ForStatement');
var BinaryExpression = require('./ast/BinaryExpression');
var UpdateExpression = require('./ast/UpdateExpression');
var UnaryExpression = require('./ast/UnaryExpression');
var MemberExpression = require('./ast/MemberExpression');
var Code = require('./ast/Code');
var InvokeMacro = require('./ast/InvokeMacro');
var Macro = require('./ast/Macro');
var ConditionalExpression = require('./ast/ConditionalExpression');
var NewExpression = require('./ast/NewExpression');
var ObjectExpression = require('./ast/ObjectExpression');
var ArrayExpression = require('./ast/ArrayExpression');
var Property = require('./ast/Property');
var VariableDeclarator = require('./ast/VariableDeclarator');
var ThisExpression = require('./ast/ThisExpression');
var Expression = require('./ast/Expression');
var Scriptlet = require('./ast/Scriptlet');
var ContainerNode = require('./ast/ContainerNode');
var WhileStatement = require('./ast/WhileStatement');
var DocumentType = require('./ast/DocumentType');
var Declaration = require('./ast/Declaration');
var SequenceExpression = require('./ast/SequenceExpression');
var CustomTag = require('./ast/CustomTag');
var Node = require("./ast/Node");
var Program = require("./ast/Program");
var TemplateRoot = require("./ast/TemplateRoot");
var FunctionDeclaration = require("./ast/FunctionDeclaration");
var FunctionCall = require("./ast/FunctionCall");
var Literal = require("./ast/Literal");
var Identifier = require("./ast/Identifier");
var Comment = require("./ast/Comment");
var If = require("./ast/If");
var ElseIf = require("./ast/ElseIf");
var Else = require("./ast/Else");
var Assignment = require("./ast/Assignment");
var BinaryExpression = require("./ast/BinaryExpression");
var LogicalExpression = require("./ast/LogicalExpression");
var Vars = require("./ast/Vars");
var Return = require("./ast/Return");
var HtmlElement = require("./ast/HtmlElement");
var Html = require("./ast/Html");
var Text = require("./ast/Text");
var ForEach = require("./ast/ForEach");
var ForEachProp = require("./ast/ForEachProp");
var ForRange = require("./ast/ForRange");
var HtmlComment = require("./ast/HtmlComment");
var SelfInvokingFunction = require("./ast/SelfInvokingFunction");
var ForStatement = require("./ast/ForStatement");
var UpdateExpression = require("./ast/UpdateExpression");
var UnaryExpression = require("./ast/UnaryExpression");
var MemberExpression = require("./ast/MemberExpression");
var Code = require("./ast/Code");
var InvokeMacro = require("./ast/InvokeMacro");
var Macro = require("./ast/Macro");
var ConditionalExpression = require("./ast/ConditionalExpression");
var NewExpression = require("./ast/NewExpression");
var ObjectExpression = require("./ast/ObjectExpression");
var ArrayExpression = require("./ast/ArrayExpression");
var Property = require("./ast/Property");
var VariableDeclarator = require("./ast/VariableDeclarator");
var ThisExpression = require("./ast/ThisExpression");
var Expression = require("./ast/Expression");
var Scriptlet = require("./ast/Scriptlet");
var ContainerNode = require("./ast/ContainerNode");
var WhileStatement = require("./ast/WhileStatement");
var DocumentType = require("./ast/DocumentType");
var Declaration = require("./ast/Declaration");
var SequenceExpression = require("./ast/SequenceExpression");
var CustomTag = require("./ast/CustomTag");
var parseExpression = require('./util/parseExpression');
var parseStatement = require('./util/parseStatement');
var parseJavaScriptArgs = require('./util/parseJavaScriptArgs');
var replacePlaceholderEscapeFuncs = require('./util/replacePlaceholderEscapeFuncs');
var isValidJavaScriptIdentifier = require('./util/isValidJavaScriptIdentifier');
var parseExpression = require("./util/parseExpression");
var parseStatement = require("./util/parseStatement");
var parseJavaScriptArgs = require("./util/parseJavaScriptArgs");
var replacePlaceholderEscapeFuncs = require("./util/replacePlaceholderEscapeFuncs");
var isValidJavaScriptIdentifier = require("./util/isValidJavaScriptIdentifier");
var DEFAULT_BUILDER;
function makeNode(arg) {
if (typeof arg === 'string') {
if (typeof arg === "string") {
return parseExpression(arg, DEFAULT_BUILDER);
} else if (arg instanceof Node) {
return arg;
} else if (arg == null) {
return undefined;
} else if (Array.isArray(arg)) {
return arg.map((arg) => {
return arg.map(arg => {
return makeNode(arg);
});
} else {
throw new Error('Argument should be a string or Node or null. Actual: ' + arg);
throw new Error(
"Argument should be a string or Node or null. Actual: " + arg
);
}
}
var literalNull = new Literal({value: null});
var literalUndefined = new Literal({value: undefined});
var literalTrue = new Literal({value: true});
var literalFalse = new Literal({value: false});
var identifierOut = new Identifier({name: 'out'});
var identifierRequire = new Identifier({name: 'require'});
var literalNull = new Literal({ value: null });
var literalUndefined = new Literal({ value: undefined });
var literalTrue = new Literal({ value: true });
var literalFalse = new Literal({ value: false });
var identifierOut = new Identifier({ name: "out" });
var identifierRequire = new Identifier({ name: "require" });
class Builder {
arrayExpression(elements) {
@ -88,38 +89,38 @@ class Builder {
elements = [elements];
}
for (var i=0; i<elements.length; i++) {
for (var i = 0; i < elements.length; i++) {
elements[i] = makeNode(elements[i]);
}
} else {
elements = [];
}
return new ArrayExpression({elements});
return new ArrayExpression({ elements });
}
assignment(left, right, operator) {
if (operator == null) {
operator = '=';
operator = "=";
}
left = makeNode(left);
right = makeNode(right);
return new Assignment({left, right, operator});
return new Assignment({ left, right, operator });
}
binaryExpression(left, operator, right) {
left = makeNode(left);
right = makeNode(right);
return new BinaryExpression({left, operator, right});
return new BinaryExpression({ left, operator, right });
}
sequenceExpression(expressions) {
expressions = makeNode(expressions);
return new SequenceExpression({expressions});
return new SequenceExpression({ expressions });
}
code(value) {
return new Code({value});
return new Code({ value });
}
computedMemberExpression(object, property) {
@ -127,7 +128,7 @@ class Builder {
property = makeNode(property);
let computed = true;
return new MemberExpression({object, property, computed});
return new MemberExpression({ object, property, computed });
}
/**
@ -138,32 +139,34 @@ class Builder {
*/
concat(args) {
var prev;
let operator = '+';
args = Array.isArray(args) ? args : Array.prototype.slice.call(arguments, 0);
let operator = "+";
args = Array.isArray(args)
? args
: Array.prototype.slice.call(arguments, 0);
for (var i=1; i<args.length; i++) {
for (var i = 1; i < args.length; i++) {
var left;
var right = makeNode(args[i]);
if (i === 1) {
left = makeNode(args[i-1]);
left = makeNode(args[i - 1]);
} else {
left = prev;
}
prev = new BinaryExpression({left, operator, right});
prev = new BinaryExpression({ left, operator, right });
}
return prev;
}
conditionalExpression(test, consequent, alternate) {
return new ConditionalExpression({test, consequent, alternate});
return new ConditionalExpression({ test, consequent, alternate });
}
containerNode(type, codeGenerator) {
if (typeof type === 'function') {
if (typeof type === "function") {
codeGenerator = arguments[0];
type = 'ContainerNode';
type = "ContainerNode";
}
var node = new ContainerNode(type);
@ -178,25 +181,25 @@ class Builder {
}
declaration(declaration) {
return new Declaration({declaration});
return new Declaration({ declaration });
}
documentType(documentType) {
return new DocumentType({documentType});
return new DocumentType({ documentType });
}
elseStatement(body) {
return new Else({body});
return new Else({ body });
}
elseIfStatement(test, body, elseStatement) {
test = makeNode(test);
return new ElseIf({test, body, else: elseStatement});
return new ElseIf({ test, body, else: elseStatement });
}
expression(value) {
return new Expression({value});
return new Expression({ value });
}
forEach(varName, inExpression, body) {
@ -206,7 +209,7 @@ class Builder {
} else {
varName = makeNode(varName);
inExpression = makeNode(inExpression);
return new ForEach({varName, in: inExpression, body});
return new ForEach({ varName, in: inExpression, body });
}
}
@ -218,7 +221,12 @@ class Builder {
nameVarName = makeNode(nameVarName);
valueVarName = makeNode(valueVarName);
inExpression = makeNode(inExpression);
return new ForEachProp({nameVarName, valueVarName, in: inExpression, body});
return new ForEachProp({
nameVarName,
valueVarName,
in: inExpression,
body
});
}
}
@ -233,7 +241,7 @@ class Builder {
step = makeNode(step);
body = makeNode(body);
return new ForRange({varName, from, to, step, body});
return new ForRange({ varName, from, to, step, body });
}
}
@ -245,7 +253,7 @@ class Builder {
init = makeNode(init);
test = makeNode(test);
update = makeNode(update);
return new ForStatement({init, test, update, body});
return new ForStatement({ init, test, update, body });
}
}
@ -257,79 +265,86 @@ class Builder {
throw new Error('"args" should be an array');
}
for (var i=0; i<args.length; i++) {
for (var i = 0; i < args.length; i++) {
args[i] = makeNode(args[i]);
}
} else {
args = [];
}
return new FunctionCall({callee, args});
return new FunctionCall({ callee, args });
}
functionDeclaration(name, params, body) {
return new FunctionDeclaration({name, params, body});
return new FunctionDeclaration({ name, params, body });
}
html(argument) {
argument = makeNode(argument);
return new Html({argument});
return new Html({ argument });
}
htmlComment(comment) {
return new HtmlComment({comment});
return new HtmlComment({ comment });
}
comment(comment) {
return new Comment({comment});
return new Comment({ comment });
}
htmlElement(tagName, attributes, body, argument, openTagOnly, selfClosed) {
if (typeof tagName === 'object' && !(tagName instanceof Node)) {
if (typeof tagName === "object" && !(tagName instanceof Node)) {
let def = arguments[0];
return new HtmlElement(def);
} else {
return new HtmlElement({tagName, attributes, body, argument, openTagOnly, selfClosed});
return new HtmlElement({
tagName,
attributes,
body,
argument,
openTagOnly,
selfClosed
});
}
}
htmlLiteral(htmlCode) {
var argument = new Literal({value: htmlCode});
return new Html({argument});
var argument = new Literal({ value: htmlCode });
return new Html({ argument });
}
identifier(name) {
ok(typeof name === 'string', '"name" should be a string');
ok(typeof name === "string", '"name" should be a string');
if (!isValidJavaScriptIdentifier(name)) {
var error = new Error('Invalid JavaScript identifier: ' + name);
error.code = 'INVALID_IDENTIFIER';
var error = new Error("Invalid JavaScript identifier: " + name);
error.code = "INVALID_IDENTIFIER";
throw error;
}
return new Identifier({name});
return new Identifier({ name });
}
identifierOut(name) {
identifierOut() {
return identifierOut;
}
ifStatement(test, body, elseStatement) {
test = makeNode(test);
return new If({test, body, else: elseStatement});
return new If({ test, body, else: elseStatement });
}
invokeMacro(name, args, body) {
return new InvokeMacro({name, args, body});
return new InvokeMacro({ name, args, body });
}
invokeMacroFromEl(el) {
return new InvokeMacro({el});
return new InvokeMacro({ el });
}
literal(value) {
return new Literal({value});
return new Literal({ value });
}
literalFalse() {
@ -351,28 +366,32 @@ class Builder {
logicalExpression(left, operator, right) {
left = makeNode(left);
right = makeNode(right);
return new LogicalExpression({left, operator, right});
return new LogicalExpression({ left, operator, right });
}
macro(name, params, body) {
return new Macro({name, params, body});
return new Macro({ name, params, body });
}
memberExpression(object, property, computed) {
object = makeNode(object);
property = makeNode(property);
return new MemberExpression({object, property, computed});
return new MemberExpression({ object, property, computed });
}
moduleExports(value) {
let object = new Identifier({name: 'module'});
let property = new Identifier({name: 'exports'});
let object = new Identifier({ name: "module" });
let property = new Identifier({ name: "exports" });
var moduleExports = new MemberExpression({object, property });
var moduleExports = new MemberExpression({ object, property });
if (value) {
return new Assignment({left: moduleExports, right: value, operator: '='});
return new Assignment({
left: moduleExports,
right: value,
operator: "="
});
} else {
return moduleExports;
}
@ -381,9 +400,9 @@ class Builder {
negate(argument) {
argument = makeNode(argument);
var operator = '!';
var operator = "!";
var prefix = true;
return new UnaryExpression({argument, operator, prefix});
return new UnaryExpression({ argument, operator, prefix });
}
newExpression(callee, args) {
@ -394,20 +413,20 @@ class Builder {
args = [args];
}
for (var i=0; i<args.length; i++) {
for (var i = 0; i < args.length; i++) {
args[i] = makeNode(args[i]);
}
} else {
args = [];
}
return new NewExpression({callee, args});
return new NewExpression({ callee, args });
}
node(type, generateCode) {
if (typeof type === 'function') {
if (typeof type === "function") {
generateCode = arguments[0];
type = 'Node';
type = "Node";
}
var node = new Node(type);
@ -420,21 +439,21 @@ class Builder {
objectExpression(properties) {
if (properties) {
if (isArray(properties)) {
for (var i=0; i<properties.length; i++) {
for (var i = 0; i < properties.length; i++) {
let prop = properties[i];
prop.value = makeNode(prop.value);
}
} else {
let propertiesObject = properties;
properties = Object.keys(propertiesObject).map((key) => {
properties = Object.keys(propertiesObject).map(key => {
let value = propertiesObject[key];
if (!(value instanceof Node)) {
value = value = new Literal({value});
value = value = new Literal({ value });
}
key = new Literal({value: key});
key = new Literal({ value: key });
let property = new Property({key, value});
let property = new Property({ key, value });
return property;
});
}
@ -442,22 +461,22 @@ class Builder {
properties = [];
}
return new ObjectExpression({properties});
return new ObjectExpression({ properties });
}
parseExpression(str, options) {
ok(typeof str === 'string', '"str" should be a string expression');
parseExpression(str) {
ok(typeof str === "string", '"str" should be a string expression');
var parsed = parseExpression(str, DEFAULT_BUILDER);
return parsed;
}
parseJavaScriptArgs(args) {
ok(typeof args === 'string', '"args" should be a string');
ok(typeof args === "string", '"args" should be a string');
return parseJavaScriptArgs(args, DEFAULT_BUILDER);
}
parseStatement(str, options) {
ok(typeof str === 'string', '"str" should be a string expression');
parseStatement(str) {
ok(typeof str === "string", '"str" should be a string expression');
var parsed = parseStatement(str, DEFAULT_BUILDER);
return parsed;
}
@ -467,7 +486,7 @@ class Builder {
}
program(body) {
return new Program({body});
return new Program({ body });
}
property(key, value, computed) {
@ -475,41 +494,41 @@ class Builder {
value = makeNode(value);
computed = computed === true;
return new Property({key, value, computed});
return new Property({ key, value, computed });
}
renderBodyFunction(body, params) {
let name = 'renderBody';
let name = "renderBody";
if (!params) {
params = [new Identifier({name: 'out'})];
params = [new Identifier({ name: "out" })];
}
return new FunctionDeclaration({name, params, body});
return new FunctionDeclaration({ name, params, body });
}
require(path) {
path = makeNode(path);
let callee = identifierRequire;
let args = [ path ];
return new FunctionCall({callee, args});
let args = [path];
return new FunctionCall({ callee, args });
}
requireResolve(path) {
path = makeNode(path);
let callee = new MemberExpression({
object: new Identifier({name: 'require'}),
property: new Identifier({name: 'resolve'})
object: new Identifier({ name: "require" }),
property: new Identifier({ name: "resolve" })
});
let args = [ path ];
return new FunctionCall({callee, args});
let args = [path];
return new FunctionCall({ callee, args });
}
returnStatement(argument) {
argument = makeNode(argument);
return new Return({argument});
return new Return({ argument });
}
scriptlet(scriptlet) {
@ -527,29 +546,29 @@ class Builder {
args = null;
}
return new SelfInvokingFunction({params, args, body});
return new SelfInvokingFunction({ params, args, body });
}
strictEquality(left, right) {
left = makeNode(left);
right = makeNode(right);
var operator = '===';
return new BinaryExpression({left, right, operator});
var operator = "===";
return new BinaryExpression({ left, right, operator });
}
templateRoot(body) {
return new TemplateRoot({body});
return new TemplateRoot({ body });
}
text(argument, escape, preserveWhitespace) {
if (typeof argument === 'object' && !(argument instanceof Node)) {
if (typeof argument === "object" && !(argument instanceof Node)) {
var def = arguments[0];
return new Text(def);
}
argument = makeNode(argument);
return new Text({argument, escape, preserveWhitespace});
return new Text({ argument, escape, preserveWhitespace });
}
thisExpression() {
@ -559,94 +578,93 @@ class Builder {
unaryExpression(argument, operator, prefix) {
argument = makeNode(argument);
return new UnaryExpression({argument, operator, prefix});
return new UnaryExpression({ argument, operator, prefix });
}
updateExpression(argument, operator, prefix) {
argument = makeNode(argument);
return new UpdateExpression({argument, operator, prefix});
return new UpdateExpression({ argument, operator, prefix });
}
variableDeclarator(id, init) {
if (typeof id === 'string') {
id = new Identifier({name: id});
if (typeof id === "string") {
id = new Identifier({ name: id });
}
if (init) {
init = makeNode(init);
}
return new VariableDeclarator({id, init});
return new VariableDeclarator({ id, init });
}
var(id, init, kind) {
if (!kind) {
kind = 'var';
kind = "var";
}
id = makeNode(id);
init = makeNode(init);
var declarations = [
new VariableDeclarator({id, init})
];
var declarations = [new VariableDeclarator({ id, init })];
return new Vars({declarations, kind});
return new Vars({ declarations, kind });
}
vars(declarations, kind) {
if (declarations) {
if (Array.isArray(declarations)) {
for (let i=0; i<declarations.length; i++) {
for (let i = 0; i < declarations.length; i++) {
var declaration = declarations[i];
if (!declaration) {
throw new Error('Invalid variable declaration');
throw new Error("Invalid variable declaration");
}
if (typeof declaration === 'string') {
if (typeof declaration === "string") {
declarations[i] = new VariableDeclarator({
id: new Identifier({name: declaration})
id: new Identifier({ name: declaration })
});
} else if (declaration instanceof Identifier) {
declarations[i] = new VariableDeclarator({
id: declaration
});
} else if (typeof declaration === 'object') {
} else if (typeof declaration === "object") {
if (!(declaration instanceof VariableDeclarator)) {
let id = declaration.id;
let init = declaration.init;
if (typeof id === 'string') {
id = new Identifier({name: id});
if (typeof id === "string") {
id = new Identifier({ name: id });
}
if (!id) {
throw new Error('Invalid variable declaration');
throw new Error("Invalid variable declaration");
}
if (init) {
init = makeNode(init);
}
declarations[i] = new VariableDeclarator({id, init});
declarations[i] = new VariableDeclarator({
id,
init
});
}
}
}
} else if (typeof declarations === 'object') {
} else if (typeof declarations === "object") {
// Convert the object into an array of variables
declarations = Object.keys(declarations).map((key) => {
let id = new Identifier({name: key});
declarations = Object.keys(declarations).map(key => {
let id = new Identifier({ name: key });
let init = makeNode(declarations[key]);
return new VariableDeclarator({ id, init });
});
}
}
return new Vars({declarations, kind});
return new Vars({ declarations, kind });
}
whileStatement(test, body) {
return new WhileStatement({test, body});
return new WhileStatement({ test, body });
}
}

View File

@ -1,14 +1,14 @@
'use strict';
"use strict";
const isArray = Array.isArray;
const Node = require('./ast/Node');
const Literal = require('./ast/Literal');
const Identifier = require('./ast/Identifier');
const HtmlElement = require('./ast/HtmlElement');
const Html = require('./ast/Html');
const ok = require('assert').ok;
const Container = require('./ast/Container');
const createError = require('raptor-util/createError');
const Node = require("./ast/Node");
const Literal = require("./ast/Literal");
const Identifier = require("./ast/Identifier");
const HtmlElement = require("./ast/HtmlElement");
const Html = require("./ast/Html");
const ok = require("assert").ok;
const Container = require("./ast/Container");
const createError = require("raptor-util/createError");
class GeneratorEvent {
constructor(node, codegen) {
@ -59,26 +59,23 @@ class FinalNodes {
}
class CodeGenerator {
constructor(context, options) {
options = options || {};
constructor(context) {
this.root = null;
this._code = '';
this.currentIndent = '';
this._code = "";
this.currentIndent = "";
this.inFunction = false;
this._doneListeners = [];
this.builder = context.builder;
this.context = context;
ok(this.builder, '"this.builder" is required');
this._codegenCodeMethodName = 'generate' +
context.outputType.toUpperCase() +
'Code';
this._codegenCodeMethodName =
"generate" + context.outputType.toUpperCase() + "Code";
}
addVar(name, value) {
@ -120,20 +117,20 @@ class CodeGenerator {
} else {
return func.call(node, node, this);
}
} catch(err) {
var errorMessage = 'Generating code for ';
} catch (err) {
var errorMessage = "Generating code for ";
if (node instanceof HtmlElement) {
errorMessage += '<'+node.tagName+'> tag';
errorMessage += "<" + node.tagName + "> tag";
} else {
errorMessage += node.type + ' node';
errorMessage += node.type + " node";
}
if (node.pos) {
errorMessage += ' ('+this.context.getPosInfo(node.pos)+')';
errorMessage += " (" + this.context.getPosInfo(node.pos) + ")";
}
errorMessage += ' failed. Error: ' + err;
errorMessage += " failed. Error: " + err;
throw createError(errorMessage, err /* cause */);
}
@ -141,12 +138,12 @@ class CodeGenerator {
_generateCode(node, finalNodes) {
if (isArray(node)) {
node.forEach((child) => {
node.forEach(child => {
this._generateCode(child, finalNodes);
});
return;
} else if (node instanceof Container) {
node.forEach((child) => {
node.forEach(child => {
if (child.container === node) {
this._generateCode(child, finalNodes);
}
@ -158,7 +155,11 @@ class CodeGenerator {
return;
}
if (typeof node === 'string' || node._finalNode || !(node instanceof Node)) {
if (
typeof node === "string" ||
node._finalNode ||
!(node instanceof Node)
) {
finalNodes.push(node);
return;
}
@ -179,9 +180,12 @@ class CodeGenerator {
}
beforeAfterEvent.isBefore = true;
beforeAfterEvent.node.emit('beforeGenerateCode', beforeAfterEvent);
this.context.emit('beforeGenerateCode:' + beforeAfterEvent.node.type, beforeAfterEvent);
this.context.emit('beforeGenerateCode', beforeAfterEvent);
beforeAfterEvent.node.emit("beforeGenerateCode", beforeAfterEvent);
this.context.emit(
"beforeGenerateCode:" + beforeAfterEvent.node.type,
beforeAfterEvent
);
this.context.emit("beforeGenerateCode", beforeAfterEvent);
if (beforeAfterEvent.insertedNodes) {
this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
@ -197,11 +201,18 @@ class CodeGenerator {
if (codeGeneratorFunc) {
node.setCodeGenerator(null);
generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
generatedCode = this._invokeCodeGenerator(
codeGeneratorFunc,
node,
false
);
if (generatedCode === null) {
node = null;
} else if (generatedCode !== undefined && generatedCode !== node) {
} else if (
generatedCode !== undefined &&
generatedCode !== node
) {
node = null;
this._generateCode(generatedCode, finalNodes);
}
@ -216,7 +227,11 @@ class CodeGenerator {
}
if (codeGeneratorFunc) {
generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, true);
generatedCode = this._invokeCodeGenerator(
codeGeneratorFunc,
node,
true
);
if (generatedCode === undefined || generatedCode === node) {
finalNodes.push(node);
@ -231,9 +246,12 @@ class CodeGenerator {
}
beforeAfterEvent.isBefore = false;
beforeAfterEvent.node.emit('afterGenerateCode', beforeAfterEvent);
this.context.emit('afterGenerateCode:' + beforeAfterEvent.node.type, beforeAfterEvent);
this.context.emit('afterGenerateCode', beforeAfterEvent);
beforeAfterEvent.node.emit("afterGenerateCode", beforeAfterEvent);
this.context.emit(
"afterGenerateCode:" + beforeAfterEvent.node.type,
beforeAfterEvent
);
this.context.emit("afterGenerateCode", beforeAfterEvent);
if (beforeAfterEvent.insertedNodes) {
this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
@ -258,7 +276,7 @@ class CodeGenerator {
let finalNodes = new FinalNodes();
var isList = typeof node.forEach === 'function';
var isList = typeof node.forEach === "function";
this._generateCode(node, finalNodes);
@ -292,12 +310,12 @@ class CodeGenerator {
let node = this._currentNode;
if (typeof message === 'object') {
if (typeof message === "object") {
let errorInfo = message;
errorInfo.node = node;
this.context.addError(errorInfo);
} else {
this.context.addError({node, message, code});
this.context.addError({ node, message, code });
}
}
@ -314,4 +332,4 @@ class CodeGenerator {
}
}
module.exports = CodeGenerator;
module.exports = CodeGenerator;

View File

@ -1,13 +1,13 @@
'use strict';
"use strict";
const isArray = Array.isArray;
const Node = require('./ast/Node');
const Literal = require('./ast/Literal');
const Identifier = require('./ast/Identifier');
const ok = require('assert').ok;
const Container = require('./ast/Container');
const Comment = require('./ast/Comment');
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
const Node = require("./ast/Node");
const Literal = require("./ast/Literal");
const Identifier = require("./ast/Identifier");
const ok = require("assert").ok;
const Container = require("./ast/Container");
const Comment = require("./ast/Comment");
const isValidJavaScriptVarName = require("./util/isValidJavaScriptVarName");
class CodeWriter {
constructor(options, builder) {
@ -15,11 +15,11 @@ class CodeWriter {
options = options || {};
this.builder = builder;
this.root = null;
this._indentStr = options.indent != null ? options.indent : ' ';
this._indentStr = options.indent != null ? options.indent : " ";
this._indentSize = this._indentStr.length;
this._code = '';
this.currentIndent = '';
this._code = "";
this.currentIndent = "";
}
getCode() {
@ -28,29 +28,30 @@ class CodeWriter {
writeBlock(body) {
if (!body) {
this.write('{}');
this.write("{}");
return;
}
if (typeof body === 'function') {
if (typeof body === "function") {
body = body();
}
if (!body ||
if (
!body ||
(Array.isArray(body) && body.length === 0) ||
(body instanceof Container && body.length === 0)) {
this.write('{}');
(body instanceof Container && body.length === 0)
) {
this.write("{}");
return;
}
this.write('{\n')
.incIndent();
this.write("{\n").incIndent();
this.writeStatements(body);
this.decIndent()
.writeLineIndent()
.write('}');
.write("}");
}
writeStatements(nodes) {
@ -61,34 +62,37 @@ class CodeWriter {
ok(nodes, '"nodes" expected');
let firstStatement = true;
var writeNode = (node) => {
if (Array.isArray(node) || (node instanceof Container)) {
var writeNode = node => {
if (Array.isArray(node) || node instanceof Container) {
node.forEach(writeNode);
return;
} else {
if (firstStatement) {
firstStatement = false;
} else {
this._write('\n');
this._write("\n");
}
this.writeLineIndent();
if (typeof node === 'string') {
if (typeof node === "string") {
this._write(node);
} else {
node.statement = true;
this.write(node);
}
if (this._code.endsWith('\n')) {
if (this._code.endsWith("\n")) {
// Do nothing
} else if (this._code.endsWith(';')) {
this._code += '\n';
} else if (this._code.endsWith('\n' + this.currentIndent) || node instanceof Comment) {
} else if (this._code.endsWith(";")) {
this._code += "\n";
} else if (
this._code.endsWith("\n" + this.currentIndent) ||
node instanceof Comment
) {
// Do nothing
} else {
this._code += ';\n';
this._code += ";\n";
}
}
};
@ -101,25 +105,28 @@ class CodeWriter {
}
write(code) {
if (code == null || code === '') {
if (code == null || code === "") {
return;
}
if (code instanceof Node) {
let node = code;
if (!node.writeCode) {
throw new Error('Node does not have a `writeCode` method: ' + JSON.stringify(node, null, 4));
throw new Error(
"Node does not have a `writeCode` method: " +
JSON.stringify(node, null, 4)
);
}
node.writeCode(this);
} else if (isArray(code) || code instanceof Container) {
code.forEach(this.write, this);
return;
} else if (typeof code === 'string') {
} else if (typeof code === "string") {
this._code += code;
} else if (typeof code === 'boolean' || typeof code === 'number') {
} else if (typeof code === "boolean" || typeof code === "number") {
this._code += code.toString();
} else {
throw new Error('Illegal argument: ' + JSON.stringify(code));
throw new Error("Illegal argument: " + JSON.stringify(code));
}
return this;
@ -132,8 +139,8 @@ class CodeWriter {
incIndent(count) {
if (count != null) {
for (let i=0; i<count; i++) {
this.currentIndent += ' ';
for (let i = 0; i < count; i++) {
this.currentIndent += " ";
}
} else {
this.currentIndent += this._indentStr;
@ -149,7 +156,8 @@ class CodeWriter {
this.currentIndent = this.currentIndent.substring(
0,
this.currentIndent.length - count);
this.currentIndent.length - count
);
return this;
}
@ -174,25 +182,25 @@ class CodeWriter {
writeLiteral(value) {
if (value === null) {
this.write('null');
this.write("null");
} else if (value === undefined) {
this.write('undefined');
} else if (typeof value === 'string') {
this.write("undefined");
} else if (typeof value === "string") {
this.write(JSON.stringify(value));
} else if (value === true) {
this.write('true');
this.write("true");
} else if (value === false) {
this.write('false');
} else if (isArray(value)) {
this.write("false");
} else if (isArray(value)) {
if (value.length === 0) {
this.write('[]');
this.write("[]");
return;
}
this.write('[\n');
this.write("[\n");
this.incIndent();
for (let i=0; i<value.length; i++) {
for (let i = 0; i < value.length; i++) {
let v = value[i];
this.writeLineIndent();
@ -204,40 +212,40 @@ class CodeWriter {
}
if (i < value.length - 1) {
this.write(',\n');
this.write(",\n");
} else {
this.write('\n');
this.write("\n");
}
}
this.decIndent();
this.writeLineIndent();
this.write(']');
} else if (typeof value === 'number') {
this.write("]");
} else if (typeof value === "number") {
this.write(value.toString());
} else if (value instanceof RegExp) {
this.write(value.toString());
} else if (typeof value === 'object') {
} else if (typeof value === "object") {
let keys = Object.keys(value);
if (keys.length === 0) {
this.write('{}');
this.write("{}");
return;
}
this.incIndent();
this.write('{\n');
this.write("{\n");
this.incIndent();
for (let i=0; i<keys.length; i++) {
for (let i = 0; i < keys.length; i++) {
let k = keys[i];
let v = value[k];
this.writeLineIndent();
if (isValidJavaScriptVarName(k)) {
this.write(k + ': ');
this.write(k + ": ");
} else {
this.write(JSON.stringify(k) + ': ');
this.write(JSON.stringify(k) + ": ");
}
if (v instanceof Node) {
@ -247,15 +255,15 @@ class CodeWriter {
}
if (i < keys.length - 1) {
this.write(',\n');
this.write(",\n");
} else {
this.write('\n');
this.write("\n");
}
}
this.decIndent();
this.writeLineIndent();
this.write('}');
this.write("}");
this.decIndent();
}
}

View File

@ -1,27 +1,26 @@
'use strict';
"use strict";
var ok = require('assert').ok;
var path = require('path');
var complain = require('complain');
var taglibLookup = require('./taglib-lookup');
var charProps = require('char-props');
var ok = require("assert").ok;
var path = require("path");
var complain = require("complain");
var taglibLookup = require("./taglib-lookup");
var charProps = require("char-props");
var UniqueVars = require('./util/UniqueVars');
var PosInfo = require('./util/PosInfo');
var CompileError = require('./CompileError');
var path = require('path');
var Node = require('./ast/Node');
var macros = require('./util/macros');
var extend = require('raptor-util/extend');
var Walker = require('./Walker');
var EventEmitter = require('events').EventEmitter;
var utilFingerprint = require('./util/finger-print');
var htmlElements = require('./util/html-elements');
var markoModules = require('./modules');
var UniqueVars = require("./util/UniqueVars");
var PosInfo = require("./util/PosInfo");
var CompileError = require("./CompileError");
var Node = require("./ast/Node");
var macros = require("./util/macros");
var extend = require("raptor-util/extend");
var Walker = require("./Walker");
var EventEmitter = require("events").EventEmitter;
var utilFingerprint = require("./util/finger-print");
var htmlElements = require("./util/html-elements");
var markoModules = require("./modules");
const markoPkgVersion = require('../../package.json').version;
const rootDir = path.join(__dirname, '../');
const isDebug = require('../build.json').isDebug;
const markoPkgVersion = require("../../package.json").version;
const rootDir = path.join(__dirname, "../");
const isDebug = require("../build.json").isDebug;
// const FLAG_IS_SVG = 1;
// const FLAG_IS_TEXTAREA = 2;
@ -29,10 +28,10 @@ const isDebug = require('../build.json').isDebug;
// const FLAG_PRESERVE = 8;
const FLAG_CUSTOM_ELEMENT = 16;
const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE';
const FLAG_PRESERVE_WHITESPACE = "PRESERVE_WHITESPACE";
function getTaglibPath(taglibPath) {
if (typeof window === 'undefined') {
if (typeof window === "undefined") {
return path.relative(process.cwd(), taglibPath);
} else {
return taglibPath;
@ -50,76 +49,80 @@ function removeExt(filename) {
function requireResolve(builder, path) {
var requireResolveNode = builder.memberExpression(
builder.identifier('require'),
builder.identifier('resolve'));
builder.identifier("require"),
builder.identifier("resolve")
);
return builder.functionCall(requireResolveNode, [ path ]);
return builder.functionCall(requireResolveNode, [path]);
}
const helpers = {
'attr': 'a',
'attrs': 'as',
'classAttr': 'ca',
'classList': 'cl',
'const': 'const',
'createElement': 'e',
'createInlineTemplate': {
vdom: { module: 'marko/runtime/vdom/helper-createInlineTemplate'},
html: { module: 'marko/runtime/html/helper-createInlineTemplate'}
attr: "a",
attrs: "as",
classAttr: "ca",
classList: "cl",
const: "const",
createElement: "e",
createInlineTemplate: {
vdom: { module: "marko/runtime/vdom/helper-createInlineTemplate" },
html: { module: "marko/runtime/html/helper-createInlineTemplate" }
},
'defineComponent': {
module: 'marko/components/helpers',
method: 'c'
defineComponent: {
module: "marko/components/helpers",
method: "c"
},
'defineComponent-legacy': {
module: 'marko/components/legacy/helpers',
method: 'c'
"defineComponent-legacy": {
module: "marko/components/legacy/helpers",
method: "c"
},
'defineWidget-legacy': {
module: 'marko/components/legacy/helpers',
method: 'w'
"defineWidget-legacy": {
module: "marko/components/legacy/helpers",
method: "w"
},
'escapeXml': 'x',
'escapeXmlAttr': 'xa',
'escapeScript': 'xs',
'escapeStyle': 'xc',
'forEach': 'f',
'forEachProp': { module: 'marko/runtime/helper-forEachProperty' },
'forEachPropStatusVar': { module: 'marko/runtime/helper-forEachPropStatusVar' },
'forEachWithStatusVar': { module: 'marko/runtime/helper-forEachWithStatusVar' },
'forRange': { module: 'marko/runtime/helper-forRange' },
'include': 'i',
'loadNestedTag': { module: 'marko/runtime/helper-loadNestedTag' },
'loadTag': 't',
'loadTemplate': { module: 'marko/runtime/helper-loadTemplate' },
'mergeNestedTagsHelper': { module: 'marko/runtime/helper-mergeNestedTags' },
'merge': { module: 'marko/runtime/helper-merge' },
'propsForPreviousNode': 'p',
'renderer': {
module: 'marko/components/helpers',
method: 'r'
escapeXml: "x",
escapeXmlAttr: "xa",
escapeScript: "xs",
escapeStyle: "xc",
forEach: "f",
forEachProp: { module: "marko/runtime/helper-forEachProperty" },
forEachPropStatusVar: {
module: "marko/runtime/helper-forEachPropStatusVar"
},
'rendererLegacy': {
module: 'marko/components/legacy/helpers',
method: 'r'
forEachWithStatusVar: {
module: "marko/runtime/helper-forEachWithStatusVar"
},
'registerComponent': {
module: 'marko/components/helpers',
method: 'rc'
forRange: { module: "marko/runtime/helper-forRange" },
include: "i",
loadNestedTag: { module: "marko/runtime/helper-loadNestedTag" },
loadTag: "t",
loadTemplate: { module: "marko/runtime/helper-loadTemplate" },
mergeNestedTagsHelper: { module: "marko/runtime/helper-mergeNestedTags" },
merge: { module: "marko/runtime/helper-merge" },
propsForPreviousNode: "p",
renderer: {
module: "marko/components/helpers",
method: "r"
},
'str': 's',
'styleAttr': {
vdom: { module: 'marko/runtime/vdom/helper-styleAttr'},
html: 'sa'
rendererLegacy: {
module: "marko/components/legacy/helpers",
method: "r"
},
'createText': 't'
registerComponent: {
module: "marko/components/helpers",
method: "rc"
},
str: "s",
styleAttr: {
vdom: { module: "marko/runtime/vdom/helper-styleAttr" },
html: "sa"
},
createText: "t"
};
class CompileContext extends EventEmitter {
constructor(src, filename, builder, options) {
super();
ok(typeof src === 'string', '"src" string is required');
ok(typeof src === "string", '"src" string is required');
ok(filename, '"filename" is required');
this.src = src;
@ -137,12 +140,14 @@ class CompileContext extends EventEmitter {
const writeVersionComment = this.options.writeVersionComment;
this.root = undefined; // root will be set by the Compiler
this.target = this.options.browser ? 'browser' : 'server';
this.outputType = this.options.output || 'html';
this.compilerType = this.options.compilerType || 'marko';
this.target = this.options.browser ? "browser" : "server";
this.outputType = this.options.output || "html";
this.compilerType = this.options.compilerType || "marko";
this.compilerVersion = this.options.compilerVersion || markoPkgVersion;
this.writeVersionComment = writeVersionComment !== 'undefined' ? writeVersionComment : true;
this.ignoreUnrecognizedTags = this.options.ignoreUnrecognizedTags === true;
this.writeVersionComment =
writeVersionComment !== "undefined" ? writeVersionComment : true;
this.ignoreUnrecognizedTags =
this.options.ignoreUnrecognizedTags === true;
this.escapeAtTags = this.options.escapeAtTags === true;
this._vars = {};
@ -158,9 +163,11 @@ class CompileContext extends EventEmitter {
this._preserveComments = null;
this.inline = this.options.inline === true;
this.useMeta = this.options.meta !== false;
this.markoModulePrefix = isDebug ? 'marko/src/' : 'marko/dist/';
this.markoModulePrefix = isDebug ? "marko/src/" : "marko/dist/";
this._moduleRuntimeTarget = this.markoModulePrefix + (this.outputType === 'vdom' ? 'vdom' : 'html');
this._moduleRuntimeTarget =
this.markoModulePrefix +
(this.outputType === "vdom" ? "vdom" : "html");
this.unrecognizedTags = [];
this._parsingFinished = false;
@ -182,8 +189,9 @@ class CompileContext extends EventEmitter {
}
getPosInfo(pos) {
var srcCharProps = this._srcCharProps || (this._srcCharProps = charProps(this.src));
let line = srcCharProps.lineAt(pos)+1;
var srcCharProps =
this._srcCharProps || (this._srcCharProps = charProps(this.src));
let line = srcCharProps.lineAt(pos) + 1;
let column = srcCharProps.columnAt(pos);
return new PosInfo(this.filename, line, column);
}
@ -218,7 +226,9 @@ class CompileContext extends EventEmitter {
popFlag(name) {
if (!this._flags.hasOwnProperty(name)) {
throw new Error('popFlag() called for "' + name + '" when flag was not set');
throw new Error(
'popFlag() called for "' + name + '" when flag was not set'
);
}
if (--this._flags[name] === 0) {
@ -245,7 +255,8 @@ class CompileContext extends EventEmitter {
var dataStack = this._dataStacks[key];
if (!dataStack || dataStack.length === 0) {
throw new Error('No data pushed for "' + key + '"'); }
throw new Error('No data pushed for "' + key + '"');
}
dataStack.pop();
@ -286,7 +297,7 @@ class CompileContext extends EventEmitter {
code,
pos
};
} else if (typeof errorInfo === 'string') {
} else if (typeof errorInfo === "string") {
let message = arguments[0];
let code = arguments[1];
let pos = arguments[2];
@ -298,7 +309,7 @@ class CompileContext extends EventEmitter {
};
}
if(errorInfo && !errorInfo.node) {
if (errorInfo && !errorInfo.node) {
errorInfo.node = this._currentNode;
}
@ -315,33 +326,36 @@ class CompileContext extends EventEmitter {
getRequirePath(targetFilename) {
if (targetFilename.startsWith(rootDir)) {
var deresolved = this.markoModulePrefix + targetFilename.substring(rootDir.length);
var deresolved =
this.markoModulePrefix +
targetFilename.substring(rootDir.length);
var ext = path.extname(deresolved);
if (ext === '.js') {
if (ext === ".js") {
deresolved = deresolved.slice(0, 0 - ext.length);
}
return deresolved.replace(/\\/g, '/');
return deresolved.replace(/\\/g, "/");
}
return markoModules.deresolve(targetFilename, this.dirname);
}
importModule(varName, path) {
if (typeof path !== 'string') {
if (typeof path !== "string") {
throw new Error('"path" should be a string');
}
if (path === 'marko') {
if (path === "marko") {
path = this.markoModulePrefix;
} else if (path.startsWith('marko/')) {
if (path.startsWith('marko/src/') || path.startsWith('marko/dist/')) {
} else {
path = this.markoModulePrefix + path.substring('marko/'.length);
} else if (path.startsWith("marko/")) {
if (
!path.startsWith("marko/src/") &&
!path.startsWith("marko/dist/")
) {
path = this.markoModulePrefix + path.substring("marko/".length);
}
}
var key = path + ':' + (varName != null);
var key = path + ":" + (varName != null);
var varId = this._imports[key];
if (varId === undefined) {
@ -350,8 +364,10 @@ class CompileContext extends EventEmitter {
var requireFuncCall = builder.require(builder.literal(path));
if (varName) {
this._imports[key] = varId = this.addStaticVar(varName, requireFuncCall);
this._imports[key] = varId = this.addStaticVar(
varName,
requireFuncCall
);
} else {
this.addStaticCode(requireFuncCall);
this._imports[key] = null;
@ -386,7 +402,7 @@ class CompileContext extends EventEmitter {
return;
}
if (typeof code === 'string') {
if (typeof code === "string") {
// Wrap the String code in a Code AST node so that
// the code will be indented properly
code = this.builder.code(code);
@ -406,7 +422,7 @@ class CompileContext extends EventEmitter {
getTagDef(tagName) {
var taglibLookup = this.taglibLookup;
if (typeof tagName === 'string') {
if (typeof tagName === "string") {
return taglibLookup.getTag(tagName);
} else {
let elNode = tagName;
@ -421,7 +437,10 @@ class CompileContext extends EventEmitter {
addErrorUnrecognizedTag(tagName, elNode) {
this.addError({
node: elNode,
message: 'Unrecognized tag: ' + tagName + ' - More details: https://github.com/marko-js/marko/wiki/Error:-Unrecognized-Tag'
message:
"Unrecognized tag: " +
tagName +
" - More details: https://github.com/marko-js/marko/wiki/Error:-Unrecognized-Tag"
});
}
@ -429,7 +448,7 @@ class CompileContext extends EventEmitter {
var elDef;
var builder = this.builder;
if (typeof tagName === 'object') {
if (typeof tagName === "object") {
elDef = tagName;
tagName = elDef.tagName;
attributes = elDef.attributes;
@ -437,39 +456,41 @@ class CompileContext extends EventEmitter {
elDef = { tagName, argument, attributes, openTagOnly, selfClosed };
}
if (elDef.tagName === '') {
elDef.tagName = tagName = 'assign';
if (elDef.tagName === "") {
elDef.tagName = tagName = "assign";
}
if (!attributes) {
attributes = elDef.attributes = [];
} else if (typeof attributes === 'object') {
} else if (typeof attributes === "object") {
if (!Array.isArray(attributes)) {
attributes = elDef.attributes = Object.keys(attributes).map((attrName) => {
var attrDef = {
name: attrName
};
attributes = elDef.attributes = Object.keys(attributes).map(
attrName => {
var attrDef = {
name: attrName
};
var val = attributes[attrName];
if (val == null) {
var val = attributes[attrName];
if (val != null) {
if (val instanceof Node) {
attrDef.value = val;
} else {
extend(attrDef, val);
}
}
} if (val instanceof Node) {
attrDef.value = val;
} else {
extend(attrDef, val);
return attrDef;
}
return attrDef;
});
);
}
} else {
throw new Error('Invalid attributes');
throw new Error("Invalid attributes");
}
var isAtTag = typeof tagName === 'string' && tagName.startsWith('@');
var isAtTag = typeof tagName === "string" && tagName.startsWith("@");
if (isAtTag && this.escapeAtTags) {
tagName = tagName.replace(/^@/, 'at_');
tagName = tagName.replace(/^@/, "at_");
elDef.tagName = tagName;
}
@ -489,16 +510,25 @@ class CompileContext extends EventEmitter {
node = builder.customTag(elNode);
node.body = node.makeContainer(node.body.items);
} else {
if (typeof tagName === 'string') {
if (typeof tagName === "string") {
tagDef = taglibLookup.getTag(tagName);
if (!tagDef && !this.isMacro(tagName) && tagName.indexOf(':') === -1) {
var customElement = htmlElements.getRegisteredElement(tagName, this.dirname);
if (
!tagDef &&
!this.isMacro(tagName) &&
tagName.indexOf(":") === -1
) {
var customElement = htmlElements.getRegisteredElement(
tagName,
this.dirname
);
if (customElement) {
elNode.customElement = customElement;
elNode.addRuntimeFlag(FLAG_CUSTOM_ELEMENT);
if (customElement.import) {
this.addDependency(this.getRequirePath(customElement.import));
this.addDependency(
this.getRequirePath(customElement.import)
);
}
} else if (!this.ignoreUnrecognizedTags) {
if (this._parsingFinished) {
@ -520,13 +550,19 @@ class CompileContext extends EventEmitter {
if (nodeFactoryFunc) {
var newNode = nodeFactoryFunc(elNode, this);
if (!(newNode instanceof Node)) {
throw new Error('Invalid node returned from node factory for tag "' + tagName + '".');
throw new Error(
'Invalid node returned from node factory for tag "' +
tagName +
'".'
);
}
if (newNode != node) {
// Make sure the body container is associated with the correct node
if (newNode.body && newNode.body !== node) {
newNode.body = newNode.makeContainer(newNode.body.items);
newNode.body = newNode.makeContainer(
newNode.body.items
);
}
node = newNode;
}
@ -547,7 +583,7 @@ class CompileContext extends EventEmitter {
var foundAttrs = {};
// Validate the attributes
attributes.forEach((attr) => {
attributes.forEach(attr => {
let attrName = attr.name;
if (!attrName) {
// Attribute will be name for placeholder attributes. For example: <div ${data.myAttrs}>
@ -564,9 +600,15 @@ class CompileContext extends EventEmitter {
//Tag doesn't allow dynamic attributes
this.addError({
node: node,
message: 'The tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '" does not support attribute "' + attrName + '"'
message:
'The tag "' +
tagName +
'" in taglib "' +
getTaglibPath(tagDef.taglibId) +
'" does not support attribute "' +
attrName +
'"'
});
}
return;
}
@ -588,10 +630,13 @@ class CompileContext extends EventEmitter {
// Add default values for any attributes. If an attribute has a declared
// default value and the attribute was not found on the element
// then add the attribute with the specified default value
tagDef.forEachAttribute((attrDef) => {
tagDef.forEachAttribute(attrDef => {
var attrName = attrDef.name;
if (attrDef.hasOwnProperty('defaultValue') && !foundAttrs.hasOwnProperty(attrName)) {
if (
attrDef.hasOwnProperty("defaultValue") &&
!foundAttrs.hasOwnProperty(attrName)
) {
attributes.push({
name: attrName,
value: builder.literal(attrDef.defaultValue)
@ -601,7 +646,14 @@ class CompileContext extends EventEmitter {
if (!foundAttrs.hasOwnProperty(attrName)) {
this.addError({
node: node,
message: 'The "' + attrName + '" attribute is required for tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '".'
message:
'The "' +
attrName +
'" attribute is required for tag "' +
tagName +
'" in taglib "' +
getTaglibPath(tagDef.taglibId) +
'".'
});
}
}
@ -638,35 +690,45 @@ class CompileContext extends EventEmitter {
}
importTemplate(relativePath, varName) {
ok(typeof relativePath === 'string', '"path" should be a string');
ok(typeof relativePath === "string", '"path" should be a string');
var builder = this.builder;
varName = varName || removeExt(path.basename(relativePath)) + '_template';
varName =
varName || removeExt(path.basename(relativePath)) + "_template";
var templateVar;
if (this.options.browser || this.options.requireTemplates) {
// When compiling a Marko template for the browser we just use `require('./template.marko')`
templateVar = this.addStaticVar(varName, builder.require(builder.literal(relativePath)));
templateVar = this.addStaticVar(
varName,
builder.require(builder.literal(relativePath))
);
} else {
// When compiling a Marko template for the server we just use `loadTemplate(require.resolve('./template.marko'))`
let loadTemplateArg = requireResolve(builder, builder.literal(relativePath));
let loadFunctionCall = builder.functionCall(this.helper('loadTemplate'), [ loadTemplateArg ]);
let loadTemplateArg = requireResolve(
builder,
builder.literal(relativePath)
);
let loadFunctionCall = builder.functionCall(
this.helper("loadTemplate"),
[loadTemplateArg]
);
templateVar = this.addStaticVar(varName, loadFunctionCall);
}
this.pushMeta('tags', relativePath, true);
this.pushMeta("tags", relativePath, true);
return templateVar;
}
addDependency(path, type, options) {
addDependency(path, type) {
var dependency;
if (type) {
dependency = { type, path };
} else {
dependency = path;
}
this.pushMeta('deps', dependency, true);
this.pushMeta("deps", dependency, true);
}
pushMeta(key, value, unique) {
@ -674,9 +736,12 @@ class CompileContext extends EventEmitter {
property = this.meta[key];
if(!property) {
if (!property) {
this.meta[key] = [value];
} else if(!unique || !property.some(e => JSON.stringify(e) === JSON.stringify(value))) {
} else if (
!unique ||
!property.some(e => JSON.stringify(e) === JSON.stringify(value))
) {
property.push(value);
}
}
@ -698,7 +763,10 @@ class CompileContext extends EventEmitter {
}
isPreserveWhitespace() {
if (this.isFlagSet(FLAG_PRESERVE_WHITESPACE) || this._preserveWhitespace === true) {
if (
this.isFlagSet(FLAG_PRESERVE_WHITESPACE) ||
this._preserveWhitespace === true
) {
return true;
}
}
@ -721,10 +789,13 @@ class CompileContext extends EventEmitter {
resolvePath(pathExpression) {
ok(pathExpression, '"pathExpression" is required');
if (pathExpression.type === 'Literal') {
if (pathExpression.type === "Literal") {
let path = pathExpression.value;
if (typeof path === 'string') {
return this.addStaticVar(path, this.builder.requireResolve(pathExpression));
if (typeof path === "string") {
return this.addStaticVar(
path,
this.builder.requireResolve(pathExpression)
);
}
}
return pathExpression;
@ -733,9 +804,9 @@ class CompileContext extends EventEmitter {
resolveTemplate(pathExpression) {
ok(pathExpression, '"pathExpression" is required');
if (pathExpression.type === 'Literal') {
if (pathExpression.type === "Literal") {
let path = pathExpression.value;
if (typeof path === 'string') {
if (typeof path === "string") {
return this.importTemplate(path);
}
}
@ -748,12 +819,12 @@ class CompileContext extends EventEmitter {
let staticNodes = [];
let staticVars = this.getStaticVars();
let staticVarNodes = Object.keys(staticVars).map((varName) => {
let staticVarNodes = Object.keys(staticVars).map(varName => {
var varInit = staticVars[varName];
return builder.variableDeclarator(varName, varInit);
});
if(additionalVars) {
if (additionalVars) {
staticVarNodes = additionalVars.concat(staticVarNodes);
}
@ -772,8 +843,14 @@ class CompileContext extends EventEmitter {
get helpersIdentifier() {
if (!this._helpersIdentifier) {
var target = this.outputType === 'vdom' ? 'marko/runtime/vdom/helpers' : 'marko/runtime/html/helpers';
this._helpersIdentifier = this.importModule('marko_helpers', target);
var target =
this.outputType === "vdom"
? "marko/runtime/vdom/helpers"
: "marko/runtime/html/helpers";
this._helpersIdentifier = this.importModule(
"marko_helpers",
target
);
}
return this._helpersIdentifier;
}
@ -783,38 +860,50 @@ class CompileContext extends EventEmitter {
if (!helperIdentifier) {
var helperInfo = helpers[name];
if (helperInfo && typeof helperInfo === 'object') {
if (helperInfo && typeof helperInfo === "object") {
if (!helperInfo.module) {
helperInfo = helperInfo[this.outputType];
}
}
if (!helperInfo) {
throw new Error('Invalid helper: ' + name);
throw new Error("Invalid helper: " + name);
}
if (typeof helperInfo === 'string') {
if (typeof helperInfo === "string") {
let methodName = helperInfo;
var methodIdentifier = this.builder.identifier(methodName);
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.memberExpression(this.helpersIdentifier, methodIdentifier));
"marko_" + name,
this.builder.memberExpression(
this.helpersIdentifier,
methodIdentifier
)
);
} else if (helperInfo && helperInfo.module) {
if (helperInfo.method) {
let moduleIdentifier = this.importModule('marko_' + helperInfo.module, helperInfo.module);
let moduleIdentifier = this.importModule(
"marko_" + helperInfo.module,
helperInfo.module
);
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.memberExpression(moduleIdentifier, helperInfo.method));
"marko_" + name,
this.builder.memberExpression(
moduleIdentifier,
helperInfo.method
)
);
} else {
helperIdentifier = this.importModule('marko_' + name, helperInfo.module);
helperIdentifier = this.importModule(
"marko_" + name,
helperInfo.module
);
}
} else {
throw new Error('Invalid helper: ' + name);
throw new Error("Invalid helper: " + name);
}
this._helpers[name] = helperIdentifier;
}
@ -844,7 +933,7 @@ class CompileContext extends EventEmitter {
optimize(rootNode) {
if (this._optimizers) {
this._optimizers.forEach((optimizer) => {
this._optimizers.forEach(optimizer => {
optimizer.optimize(rootNode, this);
});
}
@ -855,17 +944,17 @@ class CompileContext extends EventEmitter {
}
isBrowserTarget() {
return this.target === 'browser';
return this.target === "browser";
}
isServerTarget() {
return this.target === 'server';
return this.target === "server";
}
}
CompileContext.prototype.util = {
isValidJavaScriptIdentifier: require('./util/isValidJavaScriptIdentifier'),
isJavaScriptReservedWord: require('./util/isJavaScriptReservedWord')
isValidJavaScriptIdentifier: require("./util/isValidJavaScriptIdentifier"),
isJavaScriptReservedWord: require("./util/isJavaScriptReservedWord")
};
module.exports = CompileContext;

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
class CompileError {
constructor(errorInfo, context) {
@ -33,16 +33,16 @@ class CompileError {
toString() {
var pos = this.pos;
if (pos) {
pos = '[' + pos + '] ';
pos = "[" + pos + "] ";
} else {
pos = '';
pos = "";
}
var str = pos + this.message;
if (pos == null && this.node) {
str += ' (' + this.node.toString() + ')';
str += " (" + this.node.toString() + ")";
}
return str;
}
}
module.exports = CompileError;
module.exports = CompileError;

View File

@ -1,18 +1,20 @@
'use strict';
var ok = require('assert').ok;
var path = require('path');
var CodeGenerator = require('./CodeGenerator');
var CodeWriter = require('./CodeWriter');
var createError = require('raptor-util/createError');
var resolveDep = require('../components/legacy/dependencies').resolveDep;
"use strict";
var ok = require("assert").ok;
var path = require("path");
var CodeGenerator = require("./CodeGenerator");
var CodeWriter = require("./CodeWriter");
var createError = require("raptor-util/createError");
var resolveDep = require("../components/legacy/dependencies").resolveDep;
const FLAG_TRANSFORMER_APPLIED = 'transformerApply';
const FLAG_TRANSFORMER_APPLIED = "transformerApply";
function transformNode(node, context) {
try {
context.taglibLookup.forEachNodeTransformer(node, function (transformer) {
context.taglibLookup.forEachNodeTransformer(node, function(
transformer
) {
if (node.isDetached()) {
return; //The node might have been removed from the tree
return; //The node might have been removed from the tree
}
if (!node.isTransformerApplied(transformer)) {
//Check to make sure a transformer of a certain type is only applied once to a node
@ -24,11 +26,19 @@ function transformNode(node, context) {
//Set the flag to indicate that a node was transformed
// node.compiler = this;
var transformerFunc = transformer.getFunc();
transformerFunc.call(transformer, node, context); //Have the transformer process the node (NOTE: Just because a node is being processed by the transformer doesn't mean that it has to modify the parse tree)
transformerFunc.call(transformer, node, context); //Have the transformer process the node (NOTE: Just because a node is being processed by the transformer doesn't mean that it has to modify the parse tree)
}
});
} catch (e) {
throw createError(new Error('Unable to compile template at path "' + context.filename + '". Error: ' + e.message), e);
throw createError(
new Error(
'Unable to compile template at path "' +
context.filename +
'". Error: ' +
e.message
),
e
);
}
}
@ -43,14 +53,13 @@ function transformTreeHelper(node, context) {
* The checks to prevent transformers from being applied multiple times makes
* sure that this is not a problem.
*/
node.forEachChild(function (childNode) {
node.forEachChild(function(childNode) {
transformTreeHelper(childNode, context);
});
}
function transformTree(rootNode, context) {
context.taglibLookup.forEachTemplateTransformer((transformer) => {
context.taglibLookup.forEachTemplateTransformer(transformer => {
var transformFunc = transformer.getFunc();
rootNode = transformFunc(rootNode, context) || rootNode;
});
@ -63,7 +72,7 @@ function transformTree(rootNode, context) {
do {
context.clearFlag(FLAG_TRANSFORMER_APPLIED);
//Reset the flag to indicate that no transforms were yet applied to any of the nodes for this pass
transformTreeHelper(rootNode, context); //Run the transforms on the tree
transformTreeHelper(rootNode, context); //Run the transforms on the tree
} while (context.isFlagSet(FLAG_TRANSFORMER_APPLIED));
return rootNode;
@ -74,10 +83,13 @@ function handleErrors(context) {
if (context.hasErrors()) {
var errors = context.getErrors();
var message = 'An error occurred while trying to compile template at path "' + context.filename + '". Error(s) in template:\n';
var message =
'An error occurred while trying to compile template at path "' +
context.filename +
'". Error(s) in template:\n';
for (var i = 0, len = errors.length; i < len; i++) {
let error = errors[i];
message += (i + 1) + ') ' + error.toString() + '\n';
message += i + 1 + ") " + error.toString() + "\n";
}
var error = new Error(message);
error.errors = errors;
@ -86,7 +98,7 @@ function handleErrors(context) {
}
class CompiledTemplate {
constructor(ast, context, codeGenerator) {
constructor(ast, context) {
this.ast = ast;
this.context = context;
this.filename = context.filename;
@ -111,7 +123,10 @@ class CompiledTemplate {
handleErrors(this.context);
// console.log(module.id, 'FINAL AST:' + JSON.stringify(finalAST, null, 4));
var codeWriter = new CodeWriter(this.context.options, this.context.builder);
var codeWriter = new CodeWriter(
this.context.options,
this.context.builder
);
codeWriter.write(this.ast);
handleErrors(this.context);
@ -123,7 +138,7 @@ class CompiledTemplate {
}
class Compiler {
constructor(options, userOptions, inline) {
constructor(options) {
ok(options, '"options" is required');
this.builder = options.builder;
@ -134,7 +149,7 @@ class Compiler {
}
compile(src, context) {
ok(typeof src === 'string', '"src" argument should be a string');
ok(typeof src === "string", '"src" argument should be a string');
var codeGenerator = new CodeGenerator(context);
@ -143,11 +158,14 @@ class Compiler {
context._parsingFinished = true;
if (!context.ignoreUnrecognizedTags && context.unrecognizedTags) {
for(let i=0; i<context.unrecognizedTags.length; i++) {
for (let i = 0; i < context.unrecognizedTags.length; i++) {
let unrecognizedTag = context.unrecognizedTags[i];
// See if the tag is a macro
if (!context.isMacro(unrecognizedTag.tagName)) {
context.addErrorUnrecognizedTag(unrecognizedTag.tagName, unrecognizedTag.node);
context.addErrorUnrecognizedTag(
unrecognizedTag.tagName,
unrecognizedTag.node
);
}
}
}

View File

@ -1,9 +1,10 @@
'use strict';
var htmljs = require('htmljs-parser');
"use strict";
var htmljs = require("htmljs-parser");
class HtmlJsParser {
constructor(options) {
this.ignorePlaceholders = options && options.ignorePlaceholders === true;
this.ignorePlaceholders =
options && options.ignorePlaceholders === true;
}
parse(src, handlers, filename) {
@ -15,27 +16,29 @@ class HtmlJsParser {
onPlaceholder(event) {
if (event.withinBody) {
if (!event.withinString) {
handlers.handleBodyTextPlaceholder(event.value, event.escape);
handlers.handleBodyTextPlaceholder(
event.value,
event.escape
);
}
} else if (event.withinOpenTag) {
// Don't escape placeholder for dynamic attributes. For example: <div ${data.myAttrs}></div>
} else {
// placeholder within attribute
if (event.escape) {
event.value = '$escapeXml(' + event.value + ')';
event.value = "$escapeXml(" + event.value + ")";
} else {
event.value = '$noEscapeXml(' + event.value + ')';
event.value = "$noEscapeXml(" + event.value + ")";
}
}
// placeholder within content
},
onCDATA(event) {
handlers.handleCharacters(event.value, 'static-text');
handlers.handleCharacters(event.value, "static-text");
},
onOpenTagName(event, parser) {
onOpenTagName(event) {
event.selfClosed = false; // Don't allow self-closed tags
var tagParseOptions = handlers.getTagParseOptions(event);
@ -61,7 +64,6 @@ class HtmlJsParser {
},
onDocumentType(event) {
// Document type: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
// NOTE: The value will be all of the text between "<!" and ">""
handlers.handleDocumentType(event.value);
@ -86,12 +88,12 @@ class HtmlJsParser {
}
};
var parser = this.parser = htmljs.createParser(listeners, {
var parser = (this.parser = htmljs.createParser(listeners, {
ignorePlaceholders: this.ignorePlaceholders,
isOpenTagOnly: function(tagName) {
return handlers.isOpenTagOnly(tagName);
}
});
}));
parser.parse(src, filename);
}
}

View File

@ -1,28 +1,28 @@
'use strict';
"use strict";
let CodeWriter = require('./CodeWriter');
let CodeWriter = require("./CodeWriter");
function fixIndentation(lines) {
let length = lines.length;
let startLine = 0;
let endLine = length;
for (; startLine<length; startLine++) {
for (; startLine < length; startLine++) {
let line = lines[startLine];
if (line.trim() !== '') {
if (line.trim() !== "") {
break;
}
}
for (; endLine>startLine; endLine--) {
let line = lines[endLine-1];
if (line.trim() !== '') {
for (; endLine > startLine; endLine--) {
let line = lines[endLine - 1];
if (line.trim() !== "") {
break;
}
}
if (endLine === startLine) {
return '';
return "";
}
if (startLine !== 0 || endLine !== length) {
@ -33,7 +33,7 @@ function fixIndentation(lines) {
let indentToRemove = /^\s*/.exec(firstLine)[0];
if (indentToRemove) {
for (let i=0; i<lines.length; i++) {
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.startsWith(indentToRemove)) {
lines[i] = line.substring(indentToRemove.length);
@ -41,13 +41,13 @@ function fixIndentation(lines) {
}
}
return lines.join('\n');
return lines.join("\n");
}
function normalizeTemplateSrc(src) {
let lines = src.split(/\r\n|\n\r|\n/);
if (lines.length) {
if (lines[0].trim() === '') {
if (lines[0].trim() === "") {
return fixIndentation(lines);
}
}
@ -82,4 +82,4 @@ class InlineCompiler {
}
}
module.exports = InlineCompiler;
module.exports = InlineCompiler;

View File

@ -1,13 +1,13 @@
'use strict';
var ok = require('assert').ok;
var replacePlaceholderEscapeFuncs = require('./util/replacePlaceholderEscapeFuncs');
var extend = require('raptor-util/extend');
"use strict";
var ok = require("assert").ok;
var replacePlaceholderEscapeFuncs = require("./util/replacePlaceholderEscapeFuncs");
var extend = require("raptor-util/extend");
var COMPILER_ATTRIBUTE_HANDLERS = {
'preserve-whitespace': function(attr, context) {
"preserve-whitespace": function(attr, context) {
context.setPreserveWhitespace(true);
},
'preserve-comments': function(attr, context) {
"preserve-comments": function(attr, context) {
context.setPreserveComments(true);
}
};
@ -20,11 +20,11 @@ function isIEConditionalComment(comment) {
function mergeShorthandClassNames(el, shorthandClassNames, context) {
var builder = context.builder;
let classNames = shorthandClassNames.map((className) => {
let classNames = shorthandClassNames.map(className => {
return builder.parseExpression(className.value);
});
var classAttr = el.getAttributeValue('class');
var classAttr = el.getAttributeValue("class");
if (classAttr) {
classNames.push(classAttr);
}
@ -33,10 +33,14 @@ function mergeShorthandClassNames(el, shorthandClassNames, context) {
var finalClassNames = [];
for (var i=0; i<classNames.length; i++) {
for (var i = 0; i < classNames.length; i++) {
let className = classNames[i];
if (prevClassName && className.type === 'Literal' && prevClassName.type === 'Literal') {
prevClassName.value += ' ' + className.value;
if (
prevClassName &&
className.type === "Literal" &&
prevClassName.type === "Literal"
) {
prevClassName.value += " " + className.value;
} else {
finalClassNames.push(className);
prevClassName = className;
@ -44,47 +48,48 @@ function mergeShorthandClassNames(el, shorthandClassNames, context) {
}
if (finalClassNames.length === 1) {
el.setAttributeValue('class', finalClassNames[0]);
el.setAttributeValue("class", finalClassNames[0]);
} else {
el.setAttributeValue(
'class',
builder.functionCall(
context.helper('classList'),
[
builder.literal(finalClassNames)
]));
"class",
builder.functionCall(context.helper("classList"), [
builder.literal(finalClassNames)
])
);
}
}
function getParserStateForTag(parser, el, tagDef) {
var attributes = el.attributes;
if (attributes) {
for (var i=0; i<attributes.length; i++) {
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
var attrName = attr.name;
if (attrName === 'marko-body') {
if (attrName === "marko-body") {
var parseMode;
if (attr.literalValue) {
parseMode = attr.literalValue;
}
if (parseMode === 'static-text' ||
parseMode === 'parsed-text' ||
parseMode === 'html') {
if (
parseMode === "static-text" ||
parseMode === "parsed-text" ||
parseMode === "html"
) {
return parseMode;
} else {
parser.context.addError({
message: 'Value for "marko-body" should be one of the following: "static-text", "parsed-text", "html"',
code: 'ERR_INVALID_ATTR'
message:
'Value for "marko-body" should be one of the following: "static-text", "parsed-text", "html"',
code: "ERR_INVALID_ATTR"
});
return;
}
} else if (attrName === 'template-helpers') {
return 'static-text';
} else if (attrName === 'marko-init') {
return 'static-text';
} else if (attrName === "template-helpers") {
return "static-text";
} else if (attrName === "marko-init") {
return "static-text";
}
}
}
@ -122,7 +127,7 @@ class Parser {
}
parse(src, context) {
ok(typeof src === 'string', '"src" should be a string');
ok(typeof src === "string", '"src" should be a string');
ok(context, '"context" is required');
this._reset();
@ -144,12 +149,16 @@ class Parser {
handleCharacters(text, parseMode) {
var builder = this.context.builder;
var escape = parseMode !== 'html';
var escape = parseMode !== "html";
// NOTE: If parseMode is 'static-text' or 'parsed-text' then that means that special
// HTML characters may not have been escaped on the way in so we need to escape
// them on the way out
if (this.prevTextNode && this.prevTextNode.isLiteral() && this.prevTextNode.escape === escape) {
if (
this.prevTextNode &&
this.prevTextNode.isLiteral() &&
this.prevTextNode.escape === escape
) {
this.prevTextNode.argument.value += text;
} else {
this.prevTextNode = builder.text(builder.literal(text), escape);
@ -175,17 +184,23 @@ class Parser {
if (!raw) {
if (tagNameExpression) {
tagName = builder.parseExpression(tagNameExpression);
} else if (tagName === 'marko-compiler-options') {
} else if (tagName === "marko-compiler-options") {
this.parentNode.setTrimStartEnd(true);
attributes.forEach(function (attr) {
attributes.forEach(function(attr) {
let attrName = attr.name;
let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
if (!handler) {
context.addError({
code: 'ERR_INVALID_COMPILER_OPTION',
message: 'Invalid Marko compiler option of "' + attrName + '". Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS).join(', '),
code: "ERR_INVALID_COMPILER_OPTION",
message:
'Invalid Marko compiler option of "' +
attrName +
'". Allowed: ' +
Object.keys(COMPILER_ATTRIBUTE_HANDLERS).join(
", "
),
pos: el.pos,
node: el
});
@ -205,21 +220,24 @@ class Parser {
var attributeParseErrors = [];
// <div class="foo"> -> "div class=foo"
var tagString = parser.substring(el.pos, el.endPos)
.replace(/^<|\/>$|>$/g, "").trim();
var tagString = parser
.substring(el.pos, el.endPos)
.replace(/^<|\/>$|>$/g, "")
.trim();
var shouldParsedAttributes = !tagDef || tagDef.parseAttributes !== false;
var shouldParsedAttributes =
!tagDef || tagDef.parseAttributes !== false;
var parsedAttributes = [];
if (shouldParsedAttributes) {
attributes.forEach((attr) => {
attributes.forEach(attr => {
var attrName = attr.name;
var attrRawValue = attr.value;
var attrSpread;
var attrValue;
if (attr.hasOwnProperty('literalValue')) {
if (attr.hasOwnProperty("literalValue")) {
attrValue = builder.literal(attr.literalValue);
} else if (/^\.\.\./.test(attrName)) {
attrRawValue = attrName;
@ -232,27 +250,34 @@ class Parser {
attrValue = attrRawValue;
}
if (typeof attrValue === 'string') {
if (typeof attrValue === "string") {
let parsedExpression;
let valid = true;
try {
parsedExpression = builder.parseExpression(attrValue);
} catch(e) {
} catch (e) {
if (shouldParsedAttributes) {
valid = false;
attributeParseErrors.push('Invalid JavaScript expression for attribute "' + attr.name + '": ' + e);
attributeParseErrors.push(
'Invalid JavaScript expression for attribute "' +
attr.name +
'": ' +
e
);
} else {
// Attribute failed to parse. Skip it...
return;
}
}
if (valid) {
if (raw) {
attrValue = parsedExpression;
} else {
attrValue = replacePlaceholderEscapeFuncs(parsedExpression, context);
attrValue = replacePlaceholderEscapeFuncs(
parsedExpression,
context
);
}
} else {
attrValue = null;
@ -275,7 +300,12 @@ class Parser {
}
if (attrName) {
if (attrName === 'for-key' || attrName === 'for-ref' || attrName === 'w-for' || attrName.endsWith(':key')) {
if (
attrName === "for-key" ||
attrName === "for-ref" ||
attrName === "w-for" ||
attrName.endsWith(":key")
) {
context.data.hasLegacyForKey = true;
}
}
@ -312,8 +342,7 @@ class Parser {
}
if (attributeParseErrors.length) {
attributeParseErrors.forEach((e) => {
attributeParseErrors.forEach(e => {
context.addError(node, e);
});
}
@ -325,10 +354,12 @@ class Parser {
}
if (el.shorthandClassNames) {
node.rawShorthandClassNames = el.shorthandClassNames.map((className) => {
let parsed = builder.parseExpression(className.value);
return parsed.value;
});
node.rawShorthandClassNames = el.shorthandClassNames.map(
className => {
let parsed = builder.parseExpression(className.value);
return parsed.value;
}
);
}
} else {
if (el.shorthandClassNames) {
@ -336,10 +367,16 @@ class Parser {
}
if (el.shorthandId) {
if (node.hasAttribute('id')) {
context.addError(node, 'A shorthand ID cannot be used in conjunction with the "id" attribute');
if (node.hasAttribute("id")) {
context.addError(
node,
'A shorthand ID cannot be used in conjunction with the "id" attribute'
);
} else {
node.setAttributeValue('id', builder.parseExpression(el.shorthandId.value));
node.setAttributeValue(
"id",
builder.parseExpression(el.shorthandId.value)
);
}
}
}
@ -354,7 +391,7 @@ class Parser {
handleEndElement(elementName) {
if (this.raw !== true) {
if (elementName === 'marko-compiler-options') {
if (elementName === "marko-compiler-options") {
return;
}
}
@ -368,7 +405,8 @@ class Parser {
var builder = this.context.builder;
var preserveComment = this.context.isPreserveComments() ||
var preserveComment =
this.context.isPreserveComments() ||
isIEConditionalComment(comment);
if (this.raw || preserveComment) {
@ -422,7 +460,7 @@ class Parser {
}
get parentNode() {
var last = this.stack[this.stack.length-1];
var last = this.stack[this.stack.length - 1];
return last.node;
}

View File

@ -1,6 +1,6 @@
'use strict';
"use strict";
var isArray = Array.isArray;
var Container = require('./ast/Container');
var Container = require("./ast/Container");
function noop() {}
@ -53,7 +53,7 @@ class Walker {
});
if (hasRemoval) {
for (let i=array.length-1; i>=0; i--) {
for (let i = array.length - 1; i >= 0; i--) {
if (array[i] == null) {
array.splice(i, 1);
}
@ -66,7 +66,7 @@ class Walker {
}
_walkContainer(nodes) {
nodes.forEach((node) => {
nodes.forEach(node => {
var transformed = this.walk(node);
if (!transformed) {
node.container.removeChild(node);
@ -77,13 +77,15 @@ class Walker {
}
walk(node) {
if (!node || this._stopped || typeof node === 'string') {
if (!node || this._stopped || typeof node === "string") {
return node;
}
this._reset();
var parent = this._stack.length ? this._stack[this._stack.length - 1] : undefined;
var parent = this._stack.length
? this._stack[this._stack.length - 1]
: undefined;
this._stack.push(node);
@ -149,4 +151,3 @@ class Walker {
}
module.exports = Walker;

View File

@ -1,8 +1,8 @@
'use strict';
"use strict";
var ok = require('assert').ok;
var ok = require("assert").ok;
var isArray = Array.isArray;
var Container = require('./Container');
var Container = require("./Container");
class ArrayContainer extends Container {
constructor(node, array) {
@ -12,7 +12,7 @@ class ArrayContainer extends Container {
forEach(callback, thisObj) {
var array = this.array.concat([]);
for (var i=0; i<array.length; i++) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
if (item.container === this) {
callback.call(thisObj, item, i);
@ -25,7 +25,7 @@ class ArrayContainer extends Container {
var array = this.array;
var len = array.length;
for (var i=0; i<len; i++) {
for (var i = 0; i < len; i++) {
var curChild = array[i];
if (curChild === oldChild) {
array[i] = newChild;
@ -68,11 +68,11 @@ class ArrayContainer extends Container {
insertChildBefore(newChild, referenceNode) {
ok(newChild, '"newChild" is required"');
ok(referenceNode, 'Invalid reference child');
ok(referenceNode, "Invalid reference child");
var array = this.array;
var len = array.length;
for (var i=0; i<len; i++) {
for (var i = 0; i < len; i++) {
var curChild = array[i];
if (curChild === referenceNode) {
array.splice(i, 0, newChild);
@ -81,33 +81,36 @@ class ArrayContainer extends Container {
}
}
throw new Error('Reference node not found');
throw new Error("Reference node not found");
}
insertChildAfter(newChild, referenceNode) {
ok(newChild, '"newChild" is required"');
ok(referenceNode, 'Invalid reference child');
ok(referenceNode, "Invalid reference child");
var array = this.array;
var len = array.length;
for (var i=0; i<len; i++) {
for (var i = 0; i < len; i++) {
var curChild = array[i];
if (curChild === referenceNode) {
array.splice(i+1, 0, newChild);
array.splice(i + 1, 0, newChild);
newChild.container = this;
return;
}
}
throw new Error('Reference node not found');
throw new Error("Reference node not found");
}
moveChildrenTo(target) {
ok(target.appendChild, 'Node does not support appendChild(node): ' + target);
ok(
target.appendChild,
"Node does not support appendChild(node): " + target
);
var array = this.array;
var len = array.length;
for (var i=0; i<len; i++) {
for (var i = 0; i < len; i++) {
var curChild = array[i];
curChild.container = null; // Detach the child from this container
target.appendChild(curChild);
@ -118,50 +121,48 @@ class ArrayContainer extends Container {
getPreviousSibling(node) {
if (node.container !== this) {
throw new Error('Node does not belong to container: ' + node);
throw new Error("Node does not belong to container: " + node);
}
var array = this.array;
for (var i=0; i<array.length; i++) {
for (var i = 0; i < array.length; i++) {
var curNode = array[i];
if (curNode.container !== this) {
continue;
}
if (curNode === node) {
return i-1 >= 0 ? array[i-1] : undefined;
return i - 1 >= 0 ? array[i - 1] : undefined;
}
}
}
getNextSibling(node) {
if (node.container !== this) {
throw new Error('Node does not belong to container: ' + node);
throw new Error("Node does not belong to container: " + node);
}
var array = this.array;
for (var i=0; i<array.length; i++) {
for (var i = 0; i < array.length; i++) {
var curNode = array[i];
if (curNode.container !== this) {
continue;
}
if (curNode === node) {
return i+1 < array.length ? array[i+1] : undefined;
return i + 1 < array.length ? array[i + 1] : undefined;
}
}
}
forEachNextSibling(node, callback, thisObj) {
if (node.container !== this) {
throw new Error('Node does not belong to container: ' + node);
throw new Error("Node does not belong to container: " + node);
}
var array = this.array.concat([]);
var found = false;
for (var i=0; i<array.length; i++) {
for (var i = 0; i < array.length; i++) {
var curNode = array[i];
if (curNode.container !== this) {
continue;
@ -194,9 +195,9 @@ class ArrayContainer extends Container {
set items(newItems) {
if (newItems) {
ok(isArray(newItems), 'Invalid array');
ok(isArray(newItems), "Invalid array");
for (let i=0; i<newItems.length; i++) {
for (let i = 0; i < newItems.length; i++) {
newItems[i].container = this;
}
}

View File

@ -1,10 +1,10 @@
'use strict';
"use strict";
var Node = require('./Node');
var Node = require("./Node");
class ArrayExpression extends Node {
constructor(def) {
super('ArrayExpression');
super("ArrayExpression");
this.elements = def.elements;
}
@ -17,12 +17,12 @@ class ArrayExpression extends Node {
var elements = this.elements;
if (!elements || !elements.length) {
writer.write('[]');
writer.write("[]");
return;
}
writer.incIndent();
writer.write('[\n');
writer.write("[\n");
writer.incIndent();
elements.forEach((element, i) => {
@ -30,15 +30,15 @@ class ArrayExpression extends Node {
writer.write(element);
if (i < elements.length - 1) {
writer.write(',\n');
writer.write(",\n");
} else {
writer.write('\n');
writer.write("\n");
}
});
writer.decIndent();
writer.writeLineIndent();
writer.write(']');
writer.write("]");
writer.decIndent();
}
@ -48,25 +48,25 @@ class ArrayExpression extends Node {
toJSON() {
return {
type: 'ArrayExpression',
type: "ArrayExpression",
elements: this.elements
};
}
toString() {
var result = '[';
var result = "[";
var elements = this.elements;
if (elements) {
elements.forEach((element, i) => {
if (i !== 0) {
result += ', ';
result += ", ";
}
result += element.toString();
});
}
return result + ']';
return result + "]";
}
}
module.exports = ArrayExpression;
module.exports = ArrayExpression;

View File

@ -1,10 +1,10 @@
'use strict';
"use strict";
var Node = require('./Node');
var Node = require("./Node");
class Assignment extends Node {
constructor(def) {
super('Assignment');
super("Assignment");
this.left = def.left;
this.right = def.right;
this.operator = def.operator;
@ -22,18 +22,18 @@ class Assignment extends Node {
var operator = this.operator;
writer.write(left);
writer.write(' ' + (operator || '=') + ' ');
writer.write(" " + (operator || "=") + " ");
var wrap = right instanceof Assignment;
if (wrap) {
writer.write('(');
writer.write("(");
}
writer.write(right);
if (wrap) {
writer.write(')');
writer.write(")");
}
}
@ -58,22 +58,22 @@ class Assignment extends Node {
var right = this.right;
var operator = this.operator;
var result = left.toString() + ' ' + (operator || '=') + ' ';
var result = left.toString() + " " + (operator || "=") + " ";
var wrap = right instanceof Assignment;
if (wrap) {
result += '(';
result += "(";
}
result += right.toString();
if (wrap) {
result += ')';
result += ")";
}
return result;
}
}
module.exports = Assignment;
module.exports = Assignment;

View File

@ -1,10 +1,10 @@
'use strict';
"use strict";
var Node = require('./Node');
var Node = require("./Node");
class AttributePlaceholder extends Node {
constructor(def) {
super('AttributePlaceholder');
super("AttributePlaceholder");
this.value = def.value;
this.escape = def.escape;
}
@ -34,4 +34,4 @@ class AttributePlaceholder extends Node {
}
}
module.exports = AttributePlaceholder;
module.exports = AttributePlaceholder;

View File

@ -1,35 +1,35 @@
'use strict';
"use strict";
var Node = require('./Node');
var isCompoundExpression = require('../util/isCompoundExpression');
var Node = require("./Node");
var isCompoundExpression = require("../util/isCompoundExpression");
function writeCodeForOperand(node, writer) {
var wrap = isCompoundExpression(node);
if (wrap) {
writer.write('(');
writer.write("(");
}
writer.write(node);
if (wrap) {
writer.write(')');
writer.write(")");
}
}
function operandToString(node) {
var wrap = isCompoundExpression(node);
var result = '';
var result = "";
if (wrap) {
result += '(';
result += "(";
}
result += node.toString();
if (wrap) {
result += ')';
result += ")";
}
return result;
@ -37,7 +37,7 @@ function operandToString(node) {
class BinaryExpression extends Node {
constructor(def) {
super('BinaryExpression');
super("BinaryExpression");
this.left = def.left;
this.operator = def.operator;
this.right = def.right;
@ -52,19 +52,19 @@ class BinaryExpression extends Node {
var operator = this.operator;
if (!left || !right) {
throw new Error('Invalid BinaryExpression: ' + this);
throw new Error("Invalid BinaryExpression: " + this);
}
var builder = codegen.builder;
if (left.type === 'Literal' && right.type === 'Literal') {
if (operator === '+') {
if (left.type === "Literal" && right.type === "Literal") {
if (operator === "+") {
return builder.literal(left.value + right.value);
} else if (operator === '-') {
} else if (operator === "-") {
return builder.literal(left.value - right.value);
} else if (operator === '*') {
} else if (operator === "*") {
return builder.literal(left.value * right.value);
} else if (operator === '/') {
} else if (operator === "/") {
return builder.literal(left.value / right.value);
}
}
@ -78,13 +78,13 @@ class BinaryExpression extends Node {
var right = this.right;
if (!left || !right) {
throw new Error('Invalid BinaryExpression: ' + this);
throw new Error("Invalid BinaryExpression: " + this);
}
writeCodeForOperand(left, writer);
writer.write(' ');
writer.write(" ");
writer.write(operator);
writer.write(' ');
writer.write(" ");
writeCodeForOperand(right, writer);
}
@ -94,7 +94,7 @@ class BinaryExpression extends Node {
toJSON() {
return {
type: 'BinaryExpression',
type: "BinaryExpression",
left: this.left,
operator: this.operator,
right: this.right
@ -112,11 +112,17 @@ class BinaryExpression extends Node {
var right = this.right;
if (!left || !right) {
throw new Error('Invalid BinaryExpression: ' + this);
throw new Error("Invalid BinaryExpression: " + this);
}
return operandToString(left) + ' ' + operator + ' ' + operandToString(right);
return (
operandToString(left) +
" " +
operator +
" " +
operandToString(right)
);
}
}
module.exports = BinaryExpression;
module.exports = BinaryExpression;

View File

@ -1,15 +1,15 @@
'use strict';
"use strict";
var Node = require('./Node');
var adjustIndent = require('../util/adjustIndent');
var Node = require("./Node");
var adjustIndent = require("../util/adjustIndent");
class Code extends Node {
constructor(def) {
super('Code');
super("Code");
this.value = def.value;
}
generateCode(codegen) {
generateCode() {
return this;
}
@ -26,4 +26,4 @@ class Code extends Node {
}
}
module.exports = Code;
module.exports = Code;

View File

@ -1,14 +1,14 @@
'use strict';
"use strict";
const Node = require('./Node');
const Node = require("./Node");
function _isMultilineComment(comment) {
return comment && comment.indexOf('\n') !== -1;
return comment && comment.indexOf("\n") !== -1;
}
class Comment extends Node {
constructor(def) {
super('Comment');
super("Comment");
const comment = def.comment;
@ -19,7 +19,7 @@ class Comment extends Node {
}
}
generateCode(codegen) {
generateCode() {
return this;
}

View File

@ -1,10 +1,10 @@
'use strict';
"use strict";
var Node = require('./Node');
var Node = require("./Node");
class ConditionalExpression extends Node {
constructor(def) {
super('ConditionalExpression');
super("ConditionalExpression");
this.test = def.test;
this.consequent = def.consequent;
this.alternate = def.alternate;
@ -23,9 +23,9 @@ class ConditionalExpression extends Node {
var alternate = this.alternate;
writer.write(test);
writer.write(' ? ');
writer.write(" ? ");
writer.write(consequent);
writer.write(' : ');
writer.write(" : ");
writer.write(alternate);
}
@ -35,7 +35,7 @@ class ConditionalExpression extends Node {
toJSON() {
return {
type: 'ConditionalExpression',
type: "ConditionalExpression",
test: this.test,
consequent: this.consequent,
alternate: this.alternate
@ -52,8 +52,8 @@ class ConditionalExpression extends Node {
var test = this.test;
var consequent = this.consequent;
var alternate = this.alternate;
return test.toString() + ' ? ' + consequent + ' : ' + alternate;
return test.toString() + " ? " + consequent + " : " + alternate;
}
}
module.exports = ConditionalExpression;
module.exports = ConditionalExpression;

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
class Container {
constructor(node) {
@ -10,4 +10,4 @@ class Container {
}
}
module.exports = Container;
module.exports = Container;

View File

@ -1,6 +1,6 @@
'use strict';
"use strict";
var Node = require('./Node');
var Node = require("./Node");
class ContainerNode extends Node {
constructor(type) {
@ -17,4 +17,4 @@ class ContainerNode extends Node {
}
}
module.exports = ContainerNode;
module.exports = ContainerNode;

Some files were not shown because too many files have changed in this diff Show More