marko 5.21.0 → 5.21.3
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +6 -6
- package/dist/core-tags/core/__flush_here_and_after__.js +34 -22
- package/docs/10-awesome-marko-features.md +3 -4
- package/docs/class-components.md +5 -5
- package/docs/cloudflare-workers.md +4 -6
- package/docs/compiler.md +1 -1
- package/docs/core-tags.md +9 -1
- package/docs/express.md +2 -2
- package/docs/http.md +2 -2
- package/docs/koa.md +2 -2
- package/docs/marko-5-upgrade.md +3 -4
- package/docs/marko-vs-react.md +7 -7
- package/docs/redux.md +6 -6
- package/docs/rendering.md +48 -41
- package/docs/troubleshooting-streaming.md +5 -5
- package/docs/webpack.md +9 -9
- package/package.json +3 -3
- package/src/core-tags/core/__flush_here_and_after__.js +34 -22
- package/docs/server-side-rendering.md +0 -144
package/README.md
CHANGED
@@ -39,7 +39,7 @@ number of times the button has been clicked.
|
|
39
39
|
```marko
|
40
40
|
class {
|
41
41
|
onCreate() {
|
42
|
-
this.state = { count:0 };
|
42
|
+
this.state = { count: 0 };
|
43
43
|
}
|
44
44
|
increment() {
|
45
45
|
this.state.count++;
|
@@ -48,12 +48,12 @@ class {
|
|
48
48
|
|
49
49
|
style {
|
50
50
|
.count {
|
51
|
-
color
|
52
|
-
font-size:3em;
|
51
|
+
color: #09c;
|
52
|
+
font-size: 3em;
|
53
53
|
}
|
54
54
|
.example-button {
|
55
|
-
font-size:1em;
|
56
|
-
padding:0.5em;
|
55
|
+
font-size: 1em;
|
56
|
+
padding: 0.5em;
|
57
57
|
}
|
58
58
|
}
|
59
59
|
|
@@ -85,7 +85,7 @@ component style:
|
|
85
85
|
**component.js**
|
86
86
|
|
87
87
|
```js
|
88
|
-
|
88
|
+
export default {
|
89
89
|
onCreate() {
|
90
90
|
this.state = { count: 0 };
|
91
91
|
},
|
@@ -1,32 +1,44 @@
|
|
1
1
|
"use strict";const BufferedWriter = require("../../runtime/html/BufferedWriter");
|
2
2
|
|
3
3
|
module.exports = function __flushHereAndAfter__(input, out) {
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
if (out.isSync()) {
|
5
|
+
// We create an async out that we pinky promise is going to be sync in order to postpone execution of the renderBody.
|
6
|
+
out._sync = false;
|
7
|
+
const asyncOut = out.beginAsync({ last: true });
|
8
|
+
out._sync = true;
|
9
|
+
asyncOut.sync();
|
10
|
+
out.onLast(() => {
|
11
|
+
input.renderBody(asyncOut);
|
12
|
+
asyncOut.end();
|
13
|
+
});
|
14
|
+
} else {
|
15
|
+
let flushed = false;
|
16
|
+
const asyncOut = out.beginAsync({ last: true });
|
17
|
+
const nextWriter = out.writer;
|
18
|
+
|
19
|
+
out.on("c_", (writer) => {
|
20
|
+
if (writer instanceof BufferedWriter) {
|
21
|
+
if (flushed) {
|
22
|
+
const detachedOut = out.createOut();
|
23
|
+
detachedOut.sync();
|
24
|
+
input.renderBody(detachedOut);
|
25
|
+
writer._content = detachedOut.toString() + writer._content;
|
26
|
+
} else if (writer.next === nextWriter) {
|
27
|
+
asyncOut.sync();
|
28
|
+
input.renderBody(asyncOut);
|
29
|
+
asyncOut.end();
|
30
|
+
flushed = true;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
});
|
7
34
|
|
8
|
-
|
9
|
-
|
10
|
-
if (flushed) {
|
11
|
-
const detachedOut = out.createOut();
|
12
|
-
detachedOut.sync();
|
13
|
-
input.renderBody(detachedOut);
|
14
|
-
writer._content = detachedOut.toString() + writer._content;
|
15
|
-
} else if (writer.next === nextWriter) {
|
35
|
+
out.onLast(() => {
|
36
|
+
if (!flushed) {
|
16
37
|
asyncOut.sync();
|
17
38
|
input.renderBody(asyncOut);
|
18
39
|
asyncOut.end();
|
19
40
|
flushed = true;
|
20
41
|
}
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
out.onLast(() => {
|
25
|
-
if (!flushed) {
|
26
|
-
asyncOut.sync();
|
27
|
-
input.renderBody(asyncOut);
|
28
|
-
asyncOut.end();
|
29
|
-
flushed = true;
|
30
|
-
}
|
31
|
-
});
|
42
|
+
});
|
43
|
+
}
|
32
44
|
};
|
@@ -243,9 +243,8 @@ $ const searchResultsPromise = searchService.performSearch(keywords);
|
|
243
243
|
Can’t decide if you want to do server-side rendering or client-side rendering?
|
244
244
|
Why are we even talking about this in 2017? It doesn’t matter. Seriously, just
|
245
245
|
do both. Marko makes this a no-brainer since you can render a Marko template
|
246
|
-
directly to a stream (oh, and Marko will
|
247
|
-
components
|
248
|
-
server when the page loads in the browser):
|
246
|
+
directly to a stream (oh, and Marko will automatically mount UI
|
247
|
+
components rendered on the server when the page loads in the browser):
|
249
248
|
|
250
249
|
```js
|
251
250
|
require("@marko/compiler/register"); // require .marko files!
|
@@ -289,4 +288,4 @@ Coming soon: auto correction and autonomous coding
|
|
289
288
|
|
290
289
|
---
|
291
290
|
|
292
|
-
_Cover image
|
291
|
+
[_Cover image from Wikipedia_](https://commons.wikimedia.org/wiki/File:Amanhecer_no_Hercules_--.jpg)
|
package/docs/class-components.md
CHANGED
@@ -28,7 +28,7 @@ Marko makes it easy to keep your component’s class and styles next to the HTML
|
|
28
28
|
|
29
29
|
## Server-side rendering
|
30
30
|
|
31
|
-
A UI component can be rendered on the server or in the browser, but stateful component instances will be automatically mounted to the DOM in the browser for both. If a UI component tree is rendered on the server, then Marko will recreate the UI component tree in the browser with no extra code required. For more details, please see [
|
31
|
+
A UI component can be rendered on the server or in the browser, but stateful component instances will be automatically mounted to the DOM in the browser for both. If a UI component tree is rendered on the server, then Marko will recreate the UI component tree in the browser with no extra code required. For more details, please see [Rendering](/docs/rendering/).
|
32
32
|
|
33
33
|
## Single-file components
|
34
34
|
|
@@ -107,7 +107,7 @@ counter/
|
|
107
107
|
In your `component.js` file, export the component’s class:
|
108
108
|
|
109
109
|
```js
|
110
|
-
|
110
|
+
export default class {
|
111
111
|
onCreate() {
|
112
112
|
this.state = {
|
113
113
|
count: 0
|
@@ -116,7 +116,7 @@ module.exports = class {
|
|
116
116
|
increment() {
|
117
117
|
this.state.count++;
|
118
118
|
}
|
119
|
-
}
|
119
|
+
}
|
120
120
|
```
|
121
121
|
|
122
122
|
In your `index.marko` file, you can reference methods from that class with `on-*` attributes:
|
@@ -141,7 +141,7 @@ And in your `style.css`, define the styles:
|
|
141
141
|
If you target browsers that does not support classes, a plain object of methods can be exported:
|
142
142
|
|
143
143
|
```js
|
144
|
-
|
144
|
+
export default {
|
145
145
|
onCreate: function () {
|
146
146
|
this.state = {
|
147
147
|
count: 0
|
@@ -214,7 +214,7 @@ class {
|
|
214
214
|
`component-browser.js`
|
215
215
|
|
216
216
|
```js
|
217
|
-
|
217
|
+
export default {
|
218
218
|
shout() {
|
219
219
|
alert(`My favorite number is ${this.number}!`);
|
220
220
|
}
|
@@ -5,21 +5,19 @@ project for a working example.
|
|
5
5
|
|
6
6
|
## Usage
|
7
7
|
|
8
|
-
When using Marko with [Cloudflare Workers](https://workers.cloudflare.com/)
|
8
|
+
When using Marko with [Cloudflare Workers](https://workers.cloudflare.com/), make sure that Marko is loaded with a `worker` [export condition](https://nodejs.org/api/packages.html#conditional-exports). Most bundlers support defining export conditions.
|
9
9
|
|
10
|
-
After that point
|
11
|
-
|
12
|
-
You can then simply respond with the returned stream.
|
10
|
+
After that point, imported `.marko` files will export a `.stream` method that returns a worker compatible [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). You can then respond with that returned stream:
|
13
11
|
|
14
12
|
```js
|
15
|
-
import
|
13
|
+
import Template from "./index.marko";
|
16
14
|
|
17
15
|
addEventListener("fetch", event => {
|
18
16
|
event.respondWith(handleRequest(event.request));
|
19
17
|
});
|
20
18
|
|
21
19
|
async function handleRequest(request) {
|
22
|
-
return new Response(
|
20
|
+
return new Response(Template.stream(), {
|
23
21
|
headers: {
|
24
22
|
status: 200,
|
25
23
|
headers: { "content-type": "text/html;charset=UTF-8" }
|
package/docs/compiler.md
CHANGED
@@ -294,7 +294,7 @@ The hook will also receive a `types` object that matches the [@babel/types](http
|
|
294
294
|
Here is an example hook:
|
295
295
|
|
296
296
|
```js
|
297
|
-
|
297
|
+
export default (tag, types) => {
|
298
298
|
if (types.isStringLiteral(tag.node.name)) {
|
299
299
|
console.log(`Found a tag called ${tag.node.name.value}`);
|
300
300
|
tag.remove();
|
package/docs/core-tags.md
CHANGED
@@ -265,9 +265,17 @@ Optional attributes for `<await>`:
|
|
265
265
|
| ---------------: | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
266
266
|
| `timeout` | integer | An optional timeout. If reached, rejects the promise with a `TimeoutError`. |
|
267
267
|
| `name` | string | Improves debugging and ensures ordering with the `show-after` attribute. |
|
268
|
-
| `show-after` | string | Another `<await>` tag’s `name`.
|
268
|
+
| `show-after` | string | Another `<await>` tag’s `name`. Use with `client-reorder` to ensure that the current `<await>` will always render alongside or after the named `<await>`. |
|
269
269
|
| `client-reorder` | boolean | If true, anything after this `<await>` will be server-rendered before the Promise completes, then the fulfilled Promise’s result will be updated with client-side JavaScript. |
|
270
270
|
|
271
|
+
Regardless of these attributes, the promise is executed as eagerly as possible. The attributes control how to coordinate rendering with the rest of the page:
|
272
|
+
|
273
|
+
- `client-reorder` prevents `<await>` blocks from delaying the HTTP stream, at the expense of making their rendering rely on client-side JS. Useful for making non-critical page sections not block HTML streaming of important content.
|
274
|
+
|
275
|
+
- Using `show-after` with `client-reorder` ensures that the current `<await>` block will always render simultaneously with or after the named `<await>`. Useful for cutting down on [layout shift](https://web.dev/debug-layout-shifts/). `<@placeholder>`s can help fine-tune the user experience while loading.
|
276
|
+
|
277
|
+
- `timeout` is useful for limiting non-critical content from slowing down the rest of the page too much.
|
278
|
+
|
271
279
|
> **Pro Tip**: When using `timeout`, you can distinguish between `TimeoutError`s and promise rejections by checking the error’s `name`:
|
272
280
|
>
|
273
281
|
> ```marko
|
package/docs/express.md
CHANGED
@@ -30,14 +30,14 @@ By using `res.marko` you'll automatically have access to `req`, `res`, `app`, `a
|
|
30
30
|
```javascript
|
31
31
|
import express from "express";
|
32
32
|
import markoPlugin from "@marko/express";
|
33
|
-
import
|
33
|
+
import Template from "./template.marko";
|
34
34
|
|
35
35
|
const app = express();
|
36
36
|
|
37
37
|
app.use(markoPlugin()); //enable res.marko(template, data)
|
38
38
|
|
39
39
|
app.get("/", function (req, res) {
|
40
|
-
res.marko(
|
40
|
+
res.marko(Template, {
|
41
41
|
name: "Frank",
|
42
42
|
count: 30,
|
43
43
|
colors: ["red", "green", "blue"]
|
package/docs/http.md
CHANGED
@@ -7,13 +7,13 @@ project for a working example.
|
|
7
7
|
|
8
8
|
```js
|
9
9
|
import http from "http";
|
10
|
-
import
|
10
|
+
import Template from "./index.marko";
|
11
11
|
|
12
12
|
const port = 8080;
|
13
13
|
const server = http.createServer();
|
14
14
|
|
15
15
|
server.on("request", (req, res) => {
|
16
|
-
|
16
|
+
Template.render(
|
17
17
|
{
|
18
18
|
name: "Frank",
|
19
19
|
count: 30,
|
package/docs/koa.md
CHANGED
@@ -13,13 +13,13 @@ npm install koa marko --save
|
|
13
13
|
|
14
14
|
```javascript
|
15
15
|
import Koa from "koa";
|
16
|
-
import
|
16
|
+
import Template from "./index.marko";
|
17
17
|
|
18
18
|
const app = new Koa();
|
19
19
|
|
20
20
|
app.use((ctx, next) => {
|
21
21
|
ctx.type = "html";
|
22
|
-
ctx.body =
|
22
|
+
ctx.body = Template.stream({
|
23
23
|
name: "Frank",
|
24
24
|
count: 30,
|
25
25
|
colors: ["red", "green", "blue"]
|
package/docs/marko-5-upgrade.md
CHANGED
@@ -52,14 +52,13 @@ or
|
|
52
52
|
yarn upgrade marko@^5
|
53
53
|
```
|
54
54
|
|
55
|
-
> **Note**: Marko 5 has changed to using ES Modules. This means if you are using CJS modules to `require` a Marko template you will need to use the default property exported.
|
55
|
+
> **Note**: Marko 5 has changed to using ES Modules. This means if you are using CJS modules to `require` a Marko template you will need to use the `.default` property exported.
|
56
56
|
>
|
57
57
|
> ```js
|
58
58
|
> const template = require("./template.marko");
|
59
|
-
>
|
60
|
-
> // Should become
|
59
|
+
> // …should become:
|
61
60
|
> const template = require("./template.marko").default;
|
62
61
|
>
|
63
|
-
> // If
|
62
|
+
> // If already using ES Modules, things remain the same:
|
64
63
|
> import template from "./template.marko";
|
65
64
|
> ```
|
package/docs/marko-vs-react.md
CHANGED
@@ -617,20 +617,20 @@ style.less {
|
|
617
617
|
|
618
618
|
### API
|
619
619
|
|
620
|
-
Marko compiles
|
621
|
-
|
620
|
+
Marko compiles components to JavaScript modules that export their rendering APIs,
|
621
|
+
as shown below:
|
622
622
|
|
623
623
|
```js
|
624
|
-
|
625
|
-
|
626
|
-
.appendTo(document.body);
|
624
|
+
import Greeting from "./components/greeting.marko";
|
625
|
+
Greeting.renderSync({ name: "Frank" }).appendTo(document.body);
|
627
626
|
```
|
628
627
|
|
629
|
-
The same UI component can
|
628
|
+
The same UI component can render to streams, such as a writable HTTP
|
630
629
|
response stream:
|
631
630
|
|
632
631
|
```js
|
633
|
-
|
632
|
+
import Greeting from "./components/greeting.marko";
|
633
|
+
Greeting.render({ name: "John" }, res);
|
634
634
|
```
|
635
635
|
|
636
636
|
> The users of a Marko UI component do not need to know that the component was
|
package/docs/redux.md
CHANGED
@@ -17,7 +17,7 @@ The partial code below shows how a Marko UI component can connect to a Redux sto
|
|
17
17
|
### `counter.marko`
|
18
18
|
|
19
19
|
```marko
|
20
|
-
import store from './store';
|
20
|
+
import store from './store.js';
|
21
21
|
|
22
22
|
class {
|
23
23
|
onMount () {
|
@@ -40,13 +40,13 @@ class {
|
|
40
40
|
### `reducer.js`
|
41
41
|
|
42
42
|
```js
|
43
|
-
|
43
|
+
export default function (state, action) {
|
44
44
|
state = state || { value: 0 };
|
45
45
|
|
46
46
|
// Additional reducer logic here…
|
47
47
|
|
48
48
|
return state;
|
49
|
-
}
|
49
|
+
}
|
50
50
|
```
|
51
51
|
|
52
52
|
### `store.js`
|
@@ -54,8 +54,8 @@ module.exports = function (state, action) {
|
|
54
54
|
In `counter.marko`, the imported store module exports a Redux store created with the following code:
|
55
55
|
|
56
56
|
```js
|
57
|
-
|
58
|
-
|
57
|
+
import redux from "redux";
|
58
|
+
import counter from "./reducer.js";
|
59
59
|
|
60
|
-
|
60
|
+
export default redux.createStore(counter);
|
61
61
|
```
|
package/docs/rendering.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Rendering
|
2
2
|
|
3
|
-
To render a Marko view, you need to `
|
3
|
+
To render a Marko view, you need to `import` it.
|
4
4
|
|
5
5
|
_example.js_
|
6
6
|
|
7
7
|
```js
|
8
|
-
|
8
|
+
import FancyButton from "./components/fancy-button.marko";
|
9
9
|
```
|
10
10
|
|
11
11
|
> **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 CLI](https://github.com/marko-js/cli). If you are targeting the browser, you will need to use a bundler like [`lasso`](./lasso.md), [`webpack`](./webpack.md) or [`rollup`](./rollup.md).
|
@@ -15,13 +15,13 @@ Once you have a view, you can pass input data and render it:
|
|
15
15
|
_example.js_
|
16
16
|
|
17
17
|
```js
|
18
|
-
|
19
|
-
|
18
|
+
import FancyButton from "./components/fancy-button.marko";
|
19
|
+
const html = FancyButton.renderToString({ label: "Click me!" });
|
20
20
|
|
21
21
|
console.log(html);
|
22
22
|
```
|
23
23
|
|
24
|
-
The
|
24
|
+
The data passed to `renderToString` becomes available as `input` in the component, so if `fancy-button.marko` looked like this:
|
25
25
|
|
26
26
|
_./components/fancy-button.marko_
|
27
27
|
|
@@ -51,8 +51,8 @@ Many of these methods return a [`RenderResult`](#renderresult) which is an objec
|
|
51
51
|
Using `renderSync` forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
|
52
52
|
|
53
53
|
```js
|
54
|
-
|
55
|
-
var result =
|
54
|
+
import View from "./view.marko";
|
55
|
+
var result = View.renderSync({});
|
56
56
|
|
57
57
|
result.appendTo(document.body);
|
58
58
|
```
|
@@ -67,8 +67,8 @@ result.appendTo(document.body);
|
|
67
67
|
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).
|
68
68
|
|
69
69
|
```js
|
70
|
-
|
71
|
-
var resultPromise =
|
70
|
+
import View from "./view.marko";
|
71
|
+
var resultPromise = View.render({});
|
72
72
|
|
73
73
|
resultPromise.then(result => {
|
74
74
|
result.appendTo(document.body);
|
@@ -85,9 +85,9 @@ resultPromise.then(result => {
|
|
85
85
|
| return value | `AsyncStream`/`AsyncVDOMBuilder` | the async `out` render target |
|
86
86
|
|
87
87
|
```js
|
88
|
-
|
88
|
+
import View from "./view.marko";
|
89
89
|
|
90
|
-
|
90
|
+
View.render({}, (err, result) => {
|
91
91
|
result.appendTo(document.body);
|
92
92
|
});
|
93
93
|
```
|
@@ -103,12 +103,12 @@ view.render({}, (err, result) => {
|
|
103
103
|
The HTML output is written to the passed `stream`.
|
104
104
|
|
105
105
|
```js
|
106
|
-
|
107
|
-
|
106
|
+
import http from "http";
|
107
|
+
import View from "./view.marko";
|
108
108
|
|
109
109
|
http.createServer((req, res) => {
|
110
110
|
res.setHeader("content-type", "text/html");
|
111
|
-
|
111
|
+
View.render({}, res);
|
112
112
|
});
|
113
113
|
```
|
114
114
|
|
@@ -123,10 +123,10 @@ http.createServer((req, res) => {
|
|
123
123
|
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.
|
124
124
|
|
125
125
|
```js
|
126
|
-
|
127
|
-
var out =
|
126
|
+
import View from "./view.marko";
|
127
|
+
var out = View.createOut();
|
128
128
|
|
129
|
-
|
129
|
+
View.render({}, out);
|
130
130
|
|
131
131
|
out.on("finish", () => {
|
132
132
|
console.log(out.getOutput());
|
@@ -145,8 +145,8 @@ out.end();
|
|
145
145
|
Returns an HTML string and forces the render to complete synchronously. If a tag attempts to run asynchronously, an error will be thrown.
|
146
146
|
|
147
147
|
```js
|
148
|
-
|
149
|
-
var html =
|
148
|
+
import View from "./view.marko";
|
149
|
+
var html = View.renderToString({});
|
150
150
|
|
151
151
|
document.body.innerHTML = html;
|
152
152
|
```
|
@@ -162,25 +162,27 @@ document.body.innerHTML = html;
|
|
162
162
|
An HTML string is passed to the callback.
|
163
163
|
|
164
164
|
```js
|
165
|
-
|
165
|
+
import View from "./view.marko";
|
166
166
|
|
167
|
-
|
167
|
+
View.renderToString({}, (err, html) => {
|
168
168
|
document.body.innerHTML = html;
|
169
169
|
});
|
170
170
|
```
|
171
171
|
|
172
172
|
### `stream(input)`
|
173
173
|
|
174
|
-
The `stream` method returns a
|
174
|
+
The `stream` method returns a Node.js-style stream of the output HTML.
|
175
175
|
|
176
176
|
```js
|
177
|
-
|
178
|
-
|
179
|
-
|
177
|
+
import fs from "fs";
|
178
|
+
import View from "./view.marko";
|
179
|
+
const writeStream = fs.createWriteStream("output.html");
|
180
180
|
|
181
|
-
|
181
|
+
View.stream({}).pipe(writeStream);
|
182
182
|
```
|
183
183
|
|
184
|
+
This method is available on the server, but not available by default in the browser. If you need to use streams in the browser, you may `import 'marko/stream'` as part of your client-side bundle.
|
185
|
+
|
184
186
|
## RenderResult
|
185
187
|
|
186
188
|
### `getComponent()`
|
@@ -207,35 +209,42 @@ view.stream({}).pipe(writeStream);
|
|
207
209
|
|
208
210
|
## Global data
|
209
211
|
|
210
|
-
If you need to make data available
|
212
|
+
If you need to make data available to all rendered views, use the `$global` property on the input data object. This property will be removed from `input` and merged into the `out.global` property.
|
213
|
+
|
214
|
+
Global values persist across renders.
|
211
215
|
|
212
216
|
```js
|
213
|
-
|
217
|
+
View.render({
|
214
218
|
$global: {
|
215
219
|
flags: ["mobile"]
|
216
220
|
}
|
217
221
|
});
|
218
222
|
```
|
219
223
|
|
220
|
-
|
221
|
-
|
224
|
+
> **Warning:** Use `$global` with caution; it is visible in any component.
|
225
|
+
|
226
|
+
### Sending global data to browsers
|
227
|
+
|
228
|
+
⚠️ To prevent accidentally exposing sensitive data, by default **no keys** in `out.global` are sent to browsers. To serialize data to the frontend, name the desired properties in `$global.serializedGlobals`.
|
229
|
+
|
230
|
+
Values must be serializable by [the `warp10` module](https://www.npmjs.com/package/warp10).
|
222
231
|
|
223
232
|
```js
|
233
|
+
import Page from "./index.marko";
|
234
|
+
|
224
235
|
app.get("/", (req, res) => {
|
225
236
|
const ua = req.get("User-Agent");
|
226
|
-
const isIos = !!ua.match(/iPad|iPhone/);
|
227
|
-
const isAndroid = !!ua.match(/Android/);
|
228
237
|
|
229
|
-
|
238
|
+
Page.render(
|
230
239
|
{
|
231
240
|
$global: {
|
232
|
-
isIos, //
|
233
|
-
isAndroid, //
|
234
|
-
req, //
|
241
|
+
isIos: /iPad|iPhone/.test(ua), // Serialized and available on the server and browser as `out.global.isIos`
|
242
|
+
isAndroid: /Android/.test(ua), // Serialized and available on the server and browser as `out.global.isAndroid`
|
243
|
+
req, // Only available server-side and not serialized, because it’s not in `serializedGlobals`
|
235
244
|
|
236
245
|
serializedGlobals: {
|
237
|
-
isIos: true, // Tell
|
238
|
-
isAndroid: true // Tell
|
246
|
+
isIos: true, // Tell Marko to serialize `isIos`
|
247
|
+
isAndroid: true // Tell Marko to serialize `isAndroid`
|
239
248
|
}
|
240
249
|
}
|
241
250
|
},
|
@@ -244,6 +253,4 @@ app.get("/", (req, res) => {
|
|
244
253
|
});
|
245
254
|
```
|
246
255
|
|
247
|
-
|
248
|
-
|
249
|
-
Check [this PR](https://github.com/marko-js/marko/pull/672) for more details.
|
256
|
+
For details, check [#672: “Serialize only input and state on top-level server-rendered UI components”](https://github.com/marko-js/marko/pull/672).
|
@@ -35,7 +35,7 @@ Content Delivery Networks (CDNs) consider efficient streaming one of their best
|
|
35
35
|
|
36
36
|
- Some [Akamai features designed to mitigate slow backends can ironically slow down fast chunked responses](https://community.akamai.com/customers/s/question/0D50f00006n975d/enabling-chunked-transfer-encoding-responses). Try toggling off Adaptive Acceleration, Ion, mPulse, Prefetch, and/or similar performance features. Also check for the following in the configuration:
|
37
37
|
|
38
|
-
```
|
38
|
+
```xml
|
39
39
|
<network:http.buffer-response-v2>off</network:http.buffer-response-v2>
|
40
40
|
```
|
41
41
|
|
@@ -44,15 +44,15 @@ Content Delivery Networks (CDNs) consider efficient streaming one of their best
|
|
44
44
|
For extreme cases where [Node streams very small HTML chunks with its built-in compression modules](https://github.com/marko-js/marko/pull/1641), you may need to tweak the compressor stream settings. Here’s an example with `createGzip` and its `Z_PARTIAL_FLUSH` flag:
|
45
45
|
|
46
46
|
```js
|
47
|
-
|
48
|
-
|
47
|
+
import http from "http";
|
48
|
+
import zlib from "zlib";
|
49
49
|
|
50
|
-
|
50
|
+
import MarkoTemplate from "./something.marko";
|
51
51
|
|
52
52
|
http
|
53
53
|
.createServer(function (request, response) {
|
54
54
|
response.writeHead(200, { "content-type": "text/html;charset=utf-8" });
|
55
|
-
const templateStream =
|
55
|
+
const templateStream = MarkoTemplate.stream({});
|
56
56
|
const gzipStream = zlib.createGzip({
|
57
57
|
flush: zlib.constants.Z_PARTIAL_FLUSH
|
58
58
|
});
|
package/docs/webpack.md
CHANGED
@@ -116,21 +116,22 @@ export default {
|
|
116
116
|
},
|
117
117
|
```
|
118
118
|
|
119
|
-
## Multiple client
|
119
|
+
## Multiple client-side compilers
|
120
120
|
|
121
|
-
Sometimes you need
|
121
|
+
Sometimes you need multiple compilers for your client-side bundles. For example, with [`i18n`](https://github.com/webpack/webpack/tree/master/examples/i18n) or [even shipping dynamic runtime bundles to the browser](https://github.com/eBay/arc/tree/master/packages/arc-webpack).
|
122
122
|
|
123
|
-
The
|
123
|
+
The `@marko/webpack` plugin’s `.browser` property can be passed to multiple Webpack compilers. While rendering at runtime, you can provide a `$global.buildName` property to choose which assets from the Webpack compiler are included in the page.
|
124
124
|
|
125
|
-
For example with the
|
125
|
+
For example, with the Webpack internationalization plugin, you might have a config like the following:
|
126
126
|
|
127
127
|
```js
|
128
128
|
import MarkoPlugin from "@marko/webpack/plugin";
|
129
129
|
import I18nPlugin from "i18n-webpack-plugin";
|
130
|
+
import germanTranslations from "./de.json";
|
130
131
|
|
131
132
|
const languages = {
|
132
133
|
en: null,
|
133
|
-
de:
|
134
|
+
de: germanTranslations
|
134
135
|
};
|
135
136
|
|
136
137
|
const markoPlugin = new MarkoPlugin();
|
@@ -167,20 +168,19 @@ export default [
|
|
167
168
|
];
|
168
169
|
```
|
169
170
|
|
170
|
-
With the above config you can render your top
|
171
|
+
With the above config, you can render your top-level Marko template server-side with a `$global.buildName` like so:
|
171
172
|
|
172
173
|
```javascript
|
173
174
|
template.render({ $global: { buildName: "Browser-de" } });
|
174
175
|
```
|
175
176
|
|
176
|
-
|
177
|
-
Of course in this case you'll want to conditionally send the appropriate assets given a users locale. This can be some simply, like so:
|
177
|
+
That will automatically send German assets. However, what you _probably_ want instead of always serving German is conditionally sending appropriate assets for a user’s locale. This can be done like so:
|
178
178
|
|
179
179
|
```javascript
|
180
180
|
template.render({ $global: { buildName: `Browser-${req.language}` } });
|
181
181
|
```
|
182
182
|
|
183
|
-
Note
|
183
|
+
**Note:** If a bundle with the provided `buildName` does not exist, an error is thrown.
|
184
184
|
|
185
185
|
## Multiple copies of Marko
|
186
186
|
|
package/package.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "marko",
|
3
|
-
"version": "5.21.
|
3
|
+
"version": "5.21.3",
|
4
4
|
"license": "MIT",
|
5
5
|
"description": "UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.",
|
6
6
|
"dependencies": {
|
7
|
-
"@marko/compiler": "^5.21.
|
8
|
-
"@marko/translator-default": "^5.21.
|
7
|
+
"@marko/compiler": "^5.21.7",
|
8
|
+
"@marko/translator-default": "^5.21.3",
|
9
9
|
"app-module-path": "^2.2.0",
|
10
10
|
"argly": "^1.2.0",
|
11
11
|
"browser-refresh-client": "1.1.4",
|
@@ -1,32 +1,44 @@
|
|
1
1
|
const BufferedWriter = require("../../runtime/html/BufferedWriter");
|
2
2
|
|
3
3
|
module.exports = function __flushHereAndAfter__(input, out) {
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
if (out.isSync()) {
|
5
|
+
// We create an async out that we pinky promise is going to be sync in order to postpone execution of the renderBody.
|
6
|
+
out._sync = false;
|
7
|
+
const asyncOut = out.beginAsync({ last: true });
|
8
|
+
out._sync = true;
|
9
|
+
asyncOut.sync();
|
10
|
+
out.onLast(() => {
|
11
|
+
input.renderBody(asyncOut);
|
12
|
+
asyncOut.end();
|
13
|
+
});
|
14
|
+
} else {
|
15
|
+
let flushed = false;
|
16
|
+
const asyncOut = out.beginAsync({ last: true });
|
17
|
+
const nextWriter = out.writer;
|
18
|
+
|
19
|
+
out.on("___toString", writer => {
|
20
|
+
if (writer instanceof BufferedWriter) {
|
21
|
+
if (flushed) {
|
22
|
+
const detachedOut = out.createOut();
|
23
|
+
detachedOut.sync();
|
24
|
+
input.renderBody(detachedOut);
|
25
|
+
writer._content = detachedOut.toString() + writer._content;
|
26
|
+
} else if (writer.next === nextWriter) {
|
27
|
+
asyncOut.sync();
|
28
|
+
input.renderBody(asyncOut);
|
29
|
+
asyncOut.end();
|
30
|
+
flushed = true;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
});
|
7
34
|
|
8
|
-
|
9
|
-
|
10
|
-
if (flushed) {
|
11
|
-
const detachedOut = out.createOut();
|
12
|
-
detachedOut.sync();
|
13
|
-
input.renderBody(detachedOut);
|
14
|
-
writer._content = detachedOut.toString() + writer._content;
|
15
|
-
} else if (writer.next === nextWriter) {
|
35
|
+
out.onLast(() => {
|
36
|
+
if (!flushed) {
|
16
37
|
asyncOut.sync();
|
17
38
|
input.renderBody(asyncOut);
|
18
39
|
asyncOut.end();
|
19
40
|
flushed = true;
|
20
41
|
}
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
out.onLast(() => {
|
25
|
-
if (!flushed) {
|
26
|
-
asyncOut.sync();
|
27
|
-
input.renderBody(asyncOut);
|
28
|
-
asyncOut.end();
|
29
|
-
flushed = true;
|
30
|
-
}
|
31
|
-
});
|
42
|
+
});
|
43
|
+
}
|
32
44
|
};
|
@@ -1,144 +0,0 @@
|
|
1
|
-
# Server-side rendering
|
2
|
-
|
3
|
-
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:
|
4
|
-
|
5
|
-
```js
|
6
|
-
var template = require("./template"); // Import ./template.marko
|
7
|
-
|
8
|
-
module.exports = function (req, res) {
|
9
|
-
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
10
|
-
template.render({ name: "Frank" }, res);
|
11
|
-
};
|
12
|
-
```
|
13
|
-
|
14
|
-
Marko can also provide you with a `Readable` stream.
|
15
|
-
|
16
|
-
```js
|
17
|
-
var template = require("./template"); // Import ./template.marko
|
18
|
-
|
19
|
-
module.exports = function (req) {
|
20
|
-
// Return a Readable stream for someone to do something with:
|
21
|
-
return template.stream({ name: "Frank" });
|
22
|
-
};
|
23
|
-
```
|
24
|
-
|
25
|
-
> **ProTip:** Marko also provides server-side framework integrations:
|
26
|
-
>
|
27
|
-
> - [express](./express.md)
|
28
|
-
> - [koa](./koa.md)
|
29
|
-
> - [fastify](./fastify.md)
|
30
|
-
|
31
|
-
## UI Bootstrapping
|
32
|
-
|
33
|
-
When a page is rendered on the server, additional code is added to the output HTML to allow the UI to instantly boot in the browser. This additional code allows UI components rendered on the server to be mounted in the browser automatically. For each _top-level_ UI component, Marko will serialize the component's data (including `input` and `state` and any properties added to the UI component instance) so that each top-level UI component can be re-rendered and mounted when the page loads in the browser. Only a "partial" re-render is done for each top-level UI component. That is, when doing the partial re-render in the browser, the DOM is not updated and no virtual DOM is actually produced.
|
34
|
-
|
35
|
-
Marko encodes required information into attributes of rendered HTML elements and it also generates `<script>` tags that will cause UI components to be mounted. The code inside the `<script>` simply registers UI components and when the Marko runtime finally loads, all of the registered UI components will then be mounted. This allows the Marko runtime to be loaded at anytime without causing JavaScript errors.
|
36
|
-
|
37
|
-
## Bootstrapping Components
|
38
|
-
|
39
|
-
When a server-rendered page loads in the browser it's possible for marko to automatically detect UI components rendered on the server and create and mount them with the correct `state` and `input` in the browser.
|
40
|
-
|
41
|
-
### Bootstrapping: Lasso
|
42
|
-
|
43
|
-
If you are using [Lasso.js](https://github.com/lasso-js/lasso) then the bootstrapping will happen automatically as long as the JavaScript bundles for your page are included via the `<lasso-body>` tag. A typical HTML page structure will be the following:
|
44
|
-
|
45
|
-
_routes/index/template.marko_
|
46
|
-
|
47
|
-
```marko
|
48
|
-
<!DOCTYPE html>
|
49
|
-
<html lang="en">
|
50
|
-
<head>
|
51
|
-
<meta charset="UTF-8">
|
52
|
-
<title>Marko + Lasso</title>
|
53
|
-
|
54
|
-
<!-- CSS includes -->
|
55
|
-
<lasso-head/>
|
56
|
-
</head>
|
57
|
-
<body>
|
58
|
-
<!-- Top-level UI component: -->
|
59
|
-
<app/>
|
60
|
-
|
61
|
-
<!-- JS includes -->
|
62
|
-
<lasso-body/>
|
63
|
-
</body>
|
64
|
-
</html>
|
65
|
-
```
|
66
|
-
|
67
|
-
> **ProTip:** We have provided some sample apps to help you get started with Marko + Lasso
|
68
|
-
>
|
69
|
-
> - [marko-lasso](https://github.com/marko-js/examples/tree/master/examples/lasso-express)
|
70
|
-
> - [ui-components-playground](https://github.com/marko-js/examples/tree/master/examples/ui-components-playground)
|
71
|
-
|
72
|
-
### Bootstrapping: Non-Lasso
|
73
|
-
|
74
|
-
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:
|
75
|
-
|
76
|
-
1. Load/import/require all of the UI components that were rendered on the server (loading the top-level UI component is typically sufficient)
|
77
|
-
2. Call `require('marko/components').init()`
|
78
|
-
|
79
|
-
For example, if `client.js` is the entry point for your client-side application:
|
80
|
-
|
81
|
-
_routes/index/client.js_
|
82
|
-
|
83
|
-
```js
|
84
|
-
// Load the top-level UI component:
|
85
|
-
require("./components/app/index");
|
86
|
-
|
87
|
-
// Now that all of the JavaScript modules for the UI component have been
|
88
|
-
// loaded and registered we can tell marko to bootstrap/initialize the app
|
89
|
-
|
90
|
-
// Initialize and mount all of the server-rendered UI components:
|
91
|
-
require("marko/components").init();
|
92
|
-
```
|
93
|
-
|
94
|
-
> **ProTip:** We have provided some sample apps to help you get started:
|
95
|
-
>
|
96
|
-
> - [marko-webpack](https://github.com/marko-js/examples/tree/master/examples/webpack-express)
|
97
|
-
|
98
|
-
# Serialization
|
99
|
-
|
100
|
-
For each _top-level_ UI component, Marko will serialize the component's data (including `input` and `state` and any properties added to the UI component instance) down to the browser. You can control which data gets serialized by implementing [`toJSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) or by reassigning `this.input` in the UI component's `onInput(input, out)` lifecycle method as shown below:
|
101
|
-
|
102
|
-
```javascript
|
103
|
-
class {
|
104
|
-
onInput() {
|
105
|
-
// Do not serialize any input:
|
106
|
-
this.input = null;
|
107
|
-
|
108
|
-
// Serialize a new object instead of the provided input:
|
109
|
-
this.input = {
|
110
|
-
foo: 'bar'
|
111
|
-
};
|
112
|
-
}
|
113
|
-
}
|
114
|
-
```
|
115
|
-
|
116
|
-
> NOTE: Marko does allow cycles in serialized objects and Duplicate objects will only be serialized once
|
117
|
-
|
118
|
-
# Caveats
|
119
|
-
|
120
|
-
There are some caveats associated with rendering a page on the server:
|
121
|
-
|
122
|
-
- The UI component data for top-level UI components must be serializable:
|
123
|
-
- Only simple objects, numbers, strings, booleans, arrays and `Date` objects are serializable
|
124
|
-
- Functions are not serializable
|
125
|
-
- Care should be taken to avoid having Marko serialize too much data
|
126
|
-
- None of the data in `out.global` is serialized by default, but this can be changed as shown below
|
127
|
-
|
128
|
-
## Serializing globals
|
129
|
-
|
130
|
-
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:
|
131
|
-
|
132
|
-
```js
|
133
|
-
template.render(
|
134
|
-
{
|
135
|
-
$global: {
|
136
|
-
serializedGlobals: {
|
137
|
-
apiKey: true,
|
138
|
-
locale: true
|
139
|
-
}
|
140
|
-
}
|
141
|
-
},
|
142
|
-
res
|
143
|
-
);
|
144
|
-
```
|