marko 5.22.1 → 5.22.3
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +86 -86
- package/dist/core-tags/core/await/index.marko +13 -0
- package/dist/runtime/html/StringWriter.js +7 -1
- package/docs/rollup.md +266 -114
- package/docs/troubleshooting-streaming.md +8 -2
- package/package.json +5 -4
- package/src/core-tags/core/await/index.marko +13 -0
- package/src/runtime/html/StringWriter.js +7 -1
package/README.md
CHANGED
@@ -1,84 +1,75 @@
|
|
1
|
-
<
|
2
|
-
<a href="https://markojs.com/"><img src="https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-medium-cropped.png" alt="Marko" width="250" /></a>
|
3
|
-
</h1>
|
1
|
+
<div align="center">
|
4
2
|
|
5
|
-
<
|
6
|
-
<strong>A declarative, HTML-based language that makes building web apps fun 🔥</strong>
|
7
|
-
</p>
|
3
|
+
# [<img alt="Marko" src="https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-medium-cropped.png" width="250">](https://markojs.com/)
|
8
4
|
|
9
|
-
|
10
|
-
<a href="https://www.npmjs.com/package/marko"><img alt="NPM" src="https://img.shields.io/npm/v/marko.svg"/></a>
|
11
|
-
<a href="https://discord.gg/RFGxYGs"><img alt="Discord" src="https://img.shields.io/badge/discord-chat-7188da.svg"/></a>
|
12
|
-
<a href="https://github.com/marko-js/marko/actions/workflows/ci.yml">
|
13
|
-
<img src="https://github.com/marko-js/marko/actions/workflows/ci.yml/badge.svg" alt="Build status"/>
|
14
|
-
</a>
|
15
|
-
<a href="https://codecov.io/gh/marko-js/marko"><img alt="Coverage Status" src="https://codecov.io/gh/marko-js/marko/branch/master/graph/badge.svg"/></a>
|
16
|
-
<a href="http://npm-stat.com/charts.html?package=marko"><img alt="Downloads" src="https://img.shields.io/npm/dm/marko.svg"/></a>
|
17
|
-
</p>
|
5
|
+
**A declarative, HTML-based language that makes building web apps fun 🔥**
|
18
6
|
|
19
|
-
|
20
|
-
|
21
|
-
|
7
|
+
[![NPM](https://img.shields.io/npm/v/marko.svg)](https://www.npmjs.com/package/marko)
|
8
|
+
[![Discord Chat](https://img.shields.io/badge/discord-chat-7188da.svg)](https://discord.gg/RFGxYGs)
|
9
|
+
[![Continuous Integration status](https://github.com/marko-js/marko/actions/workflows/ci.yml/badge.svg)](https://github.com/marko-js/marko/actions/workflows/ci.yml)
|
10
|
+
[![Code coverage %](https://codecov.io/gh/marko-js/marko/branch/master/graph/badge.svg)](https://codecov.io/gh/marko-js/marko)
|
11
|
+
[![# of monthly downloads](https://img.shields.io/npm/dm/marko.svg)](https://npm-stat.com/charts.html?package=marko)
|
22
12
|
|
23
|
-
#
|
13
|
+
[Docs](https://markojs.com/docs/getting-started/) ∙ [Try Online](https://markojs.com/try-online/) ∙ [Contribute](#contributors) ∙ [Get Support](#community--support)
|
24
14
|
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
</div>
|
16
|
+
|
17
|
+
## Intro
|
28
18
|
|
29
|
-
Among these extensions are [conditionals
|
30
|
-
Marko supports both single-file components and components broken into separate files.
|
19
|
+
Marko is HTML _reimagined_ as a language for building dynamic and reactive user interfaces. Almost any valid HTML is valid Marko, and Marko extends HTML for building modern applications more declaratively. Among these extensions are [conditionals and lists](https://markojs.com/docs/conditionals-and-lists/), [state](https://markojs.com/docs/state/), and [components](https://markojs.com/docs/class-components/).
|
31
20
|
|
32
|
-
|
21
|
+
Marko supports both single-file components and components across separate files.
|
33
22
|
|
34
|
-
|
35
|
-
|
23
|
+
### Single-file component
|
24
|
+
|
25
|
+
The following renders a button and a counter of how many times the button has been pressed:
|
36
26
|
|
37
27
|
**click-count.marko**
|
38
28
|
|
39
29
|
```marko
|
40
30
|
class {
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
31
|
+
onCreate() {
|
32
|
+
this.state = { count: 0 };
|
33
|
+
}
|
34
|
+
increment() {
|
35
|
+
this.state.count++;
|
36
|
+
}
|
47
37
|
}
|
48
38
|
|
49
39
|
style {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
}
|
40
|
+
.count {
|
41
|
+
color: #09c;
|
42
|
+
font-size: 3em;
|
43
|
+
}
|
44
|
+
.press-me {
|
45
|
+
padding: 0.5em;
|
46
|
+
}
|
58
47
|
}
|
59
48
|
|
60
|
-
<
|
61
|
-
|
62
|
-
</
|
63
|
-
<button.
|
64
|
-
|
49
|
+
<output.count>
|
50
|
+
${state.count}
|
51
|
+
</output>
|
52
|
+
<button.press-me on-click('increment')>
|
53
|
+
Press me!
|
65
54
|
</button>
|
66
55
|
```
|
67
56
|
|
68
|
-
|
57
|
+
### Multi-file component
|
58
|
+
|
59
|
+
The same component as above, but split into:
|
69
60
|
|
70
|
-
|
71
|
-
`component.js`
|
72
|
-
component
|
61
|
+
- `index.marko` template file
|
62
|
+
- `component.js` component JS logic file
|
63
|
+
- `style.css` component styles file
|
73
64
|
|
74
65
|
**index.marko**
|
75
66
|
|
76
67
|
```marko
|
77
|
-
<
|
78
|
-
|
79
|
-
</
|
80
|
-
<button.
|
81
|
-
|
68
|
+
<output.count>
|
69
|
+
${state.count}
|
70
|
+
</output>
|
71
|
+
<button.press-me on-click('increment')>
|
72
|
+
Press me!
|
82
73
|
</button>
|
83
74
|
```
|
84
75
|
|
@@ -102,59 +93,68 @@ export default {
|
|
102
93
|
color: #09c;
|
103
94
|
font-size: 3em;
|
104
95
|
}
|
105
|
-
.
|
106
|
-
font-size: 1em;
|
96
|
+
.press-me {
|
107
97
|
padding: 0.5em;
|
108
98
|
}
|
109
99
|
```
|
110
100
|
|
111
101
|
## Concise Syntax
|
112
102
|
|
113
|
-
Marko also supports a beautifully concise syntax as an alternative to its HTML
|
114
|
-
|
103
|
+
Marko also supports [a beautifully concise syntax as an alternative](https://markojs.com/docs/concise/) to its HTML syntax:
|
104
|
+
|
105
|
+
<table><thead><tr><th>Concise syntax<th>HTML syntax
|
106
|
+
<tbody><tr>
|
107
|
+
<td>
|
115
108
|
|
116
109
|
```marko
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
<li>${color}</li>
|
121
|
-
</for>
|
122
|
-
</ul>
|
110
|
+
ul.example-list
|
111
|
+
for|color| of=[a, b, c]
|
112
|
+
li -- ${color}
|
123
113
|
```
|
124
114
|
|
115
|
+
<td>
|
116
|
+
|
125
117
|
```marko
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
118
|
+
<ul class="example-list">
|
119
|
+
<for|color| of=[a, b, c]>
|
120
|
+
<li>${color}</li>
|
121
|
+
</for>
|
122
|
+
</ul>
|
130
123
|
```
|
131
124
|
|
132
|
-
|
125
|
+
</table>
|
126
|
+
|
127
|
+
## Getting Started
|
133
128
|
|
134
129
|
1. `npm install marko`
|
135
130
|
2. Read the [docs](https://markojs.com/docs/getting-started/)
|
136
131
|
|
137
|
-
|
132
|
+
## Community & Support
|
138
133
|
|
139
|
-
|
140
|
-
|
141
|
-
|
134
|
+
<table>
|
135
|
+
<thead><tr>
|
136
|
+
<th><img alt="Stack Overflow" src="https://user-images.githubusercontent.com/1958812/56055468-619b3e00-5d0e-11e9-92ae-200c212cafb8.png" width="205">
|
137
|
+
<th><img alt="Discord" src="https://user-images.githubusercontent.com/4985201/89313514-6edbea80-d62d-11ea-8447-ca2fd8983661.png" width="162">
|
138
|
+
<th><img alt="Twitter" src="https://user-images.githubusercontent.com/1958812/56055707-07e74380-5d0f-11e9-8a59-d529fbb5a81e.png" width="53">
|
139
|
+
<tbody><tr><td>
|
140
|
+
|
141
|
+
Ask and answer [StackOverflow questions with the `#marko` tag](https://stackoverflow.com/questions/tagged/marko)<td>
|
142
142
|
|
143
|
-
|
143
|
+
Come [hang out in our Discord chat](https://discord.gg/RFGxYGs), ask questions, and discuss project direction<td>
|
144
144
|
|
145
|
-
|
145
|
+
[Tweet to `@MarkoDevTeam`](https://twitter.com/MarkoDevTeam), or with the [`#markojs` hashtag](https://twitter.com/search?q=%23markojs&f=live)
|
146
146
|
|
147
|
-
|
148
|
-
<img src="https://opencollective.com/marko-js/contributors.svg?width=890&button=false"/>
|
149
|
-
</a>
|
147
|
+
</table>
|
150
148
|
|
151
|
-
|
149
|
+
### Contributors
|
152
150
|
|
153
|
-
|
154
|
-
|
155
|
-
-
|
156
|
-
- By participating in this project you agree to abide by its [Code of Conduct](https://ebay.github.io/codeofconduct).
|
151
|
+
Marko would not be what it is without all those who have contributed ✨
|
152
|
+
|
153
|
+
[![All marko-js/marko GitHub contributors](https://opencollective.com/marko-js/contributors.svg?width=890&button=false)](https://github.com/marko-js/marko/graphs/contributors)
|
157
154
|
|
158
|
-
|
155
|
+
### Get Involved!
|
159
156
|
|
160
|
-
|
157
|
+
- Pull requests are welcome!
|
158
|
+
- Submit [GitHub issues](https://github.com/marko-js/marko/issues) for any feature enhancements, bugs, or documentation problems
|
159
|
+
- [Read the Contribution Tips and Guidelines](.github/CONTRIBUTING.md)
|
160
|
+
- Participants in this project agree to abide by [its Code of Conduct](https://github.com/eBay/.github/blob/main/CODE_OF_CONDUCT.md)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* @template T
|
3
|
+
* @typedef {{
|
4
|
+
* value?: T;
|
5
|
+
* then?: Marko.Body<[Awaited<T>], void>;
|
6
|
+
* catch?: Marko.Body<[unknown], void>;
|
7
|
+
* placeholder?: Marko.Body<[], void>;
|
8
|
+
* client-reorder?: boolean;
|
9
|
+
* name?: string;
|
10
|
+
* timeout?: number;
|
11
|
+
* show-after?: string;
|
12
|
+
* }} Input
|
13
|
+
*/
|
@@ -27,7 +27,13 @@ StringWriter.prototype = {
|
|
27
27
|
|
28
28
|
merge: function (otherWriter) {
|
29
29
|
this._content += otherWriter._content;
|
30
|
-
|
30
|
+
|
31
|
+
if (otherWriter._scripts) {
|
32
|
+
this._scripts = this._scripts ?
|
33
|
+
this._scripts + ";" + otherWriter._scripts :
|
34
|
+
otherWriter._scripts;
|
35
|
+
}
|
36
|
+
|
31
37
|
if (otherWriter._data) {
|
32
38
|
if (this._data) {
|
33
39
|
for (const key in otherWriter._data) {
|
package/docs/rollup.md
CHANGED
@@ -1,155 +1,309 @@
|
|
1
1
|
# Marko + Rollup
|
2
2
|
|
3
|
-
|
3
|
+
This is Marko’s official integration plugin for [the Rollup bundler](https://rollupjs.org/).
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```sh
|
8
|
+
npm install --save-dev \
|
9
|
+
@marko/rollup \
|
10
|
+
rollup \
|
11
|
+
@rollup/plugin-node-resolve \
|
12
|
+
@rollup/plugin-commonjs
|
7
13
|
```
|
8
14
|
|
9
|
-
|
15
|
+
> **Note**: The Marko runtime is CommonJS, so don’t forget the `@rollup/plugin-commonjs` package!
|
16
|
+
|
17
|
+
## Configuration
|
18
|
+
|
19
|
+
`@marko/rollup` exports two methods for use in [Rollup configuration files](https://rollupjs.org/guide/en/#configuration-files): `.browser()` and `.server()`.
|
20
|
+
|
21
|
+
You _probably_ want to use both, since that’ll get you…
|
22
|
+
|
23
|
+
- Automatic [`input` entrypoint configuration](https://rollupjs.org/guide/en/#input) for route-based bundle splitting
|
24
|
+
- Complete control over asset loading with [the `<rollup>` tag](#rollup-tag)
|
25
|
+
- The strengths behind why Marko exists in the first place: cooperation between servers and browsers for high performance in both
|
26
|
+
|
27
|
+
> **ProTip**: You _could_ use only `.browser()` or only `.server()` to build a completely client-side-rendered or server-side-rendered app. That would be a little odd, but you could.
|
10
28
|
|
11
|
-
|
29
|
+
### Config example
|
12
30
|
|
13
|
-
```
|
31
|
+
```js
|
14
32
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
15
33
|
import commonjs from "@rollup/plugin-commonjs";
|
16
34
|
import marko from "@marko/rollup";
|
17
35
|
|
18
|
-
|
19
|
-
|
36
|
+
const sharedPlugins = [
|
37
|
+
commonjs({
|
38
|
+
extensions: [".js", ".marko"]
|
39
|
+
}),
|
40
|
+
// If using Marko’s `style {}` blocks, you’ll need an appropriate plugin, like npmjs.com/rollup-plugin-postcss
|
41
|
+
postcss({ external: true })
|
42
|
+
]
|
43
|
+
|
44
|
+
const serverAssetsConfig = {
|
45
|
+
input: "src/start-server.js",
|
46
|
+
plugins: [
|
47
|
+
marko.server(),
|
48
|
+
nodeResolve({ preferBuiltins: true })
|
49
|
+
...sharedPlugins
|
50
|
+
]
|
51
|
+
};
|
52
|
+
const browsersAssetsConfig = {
|
20
53
|
plugins: [
|
21
54
|
marko.browser(),
|
22
|
-
nodeResolve({
|
23
|
-
|
24
|
-
extensions: [".js", ".marko"]
|
25
|
-
}),
|
26
|
-
// NOTE: The Marko runtime uses commonjs so this plugin is also required.
|
27
|
-
commonjs({
|
28
|
-
extensions: [".js", ".marko"]
|
29
|
-
}),
|
30
|
-
// If using `style` blocks with Marko you must use an appropriate plugin.
|
31
|
-
postcss({
|
32
|
-
external: true
|
33
|
-
})
|
55
|
+
nodeResolve({ browser: true })
|
56
|
+
...sharedPlugins
|
34
57
|
]
|
35
58
|
};
|
59
|
+
|
60
|
+
export default [serverAssetsConfig, browsersAssetsConfig];
|
36
61
|
```
|
37
62
|
|
38
|
-
|
63
|
+
### Advanced config example
|
39
64
|
|
40
|
-
|
65
|
+
The following configuration file is long and hairy, which may be upsetting to some viewers. However, it does show how to accomplish the following:
|
41
66
|
|
42
|
-
|
43
|
-
|
44
|
-
|
67
|
+
- Support for Rollup’s watch mode
|
68
|
+
- A bundle analyzer
|
69
|
+
- The ability to `import` JSON files to use their data
|
70
|
+
- The ability to `import` image files to use their asset URLs for `img[src]` and such
|
71
|
+
- Dead-code elimination for development-only code
|
72
|
+
- Static compression of assets for something like [NGiNX’s `gzip_static`](https://nginx.org/en/docs/http/ngx_http_gzip_static_module.html)
|
73
|
+
- A CSS preprocessor (Sass, in this case)
|
74
|
+
- Browserslist to automatically configure:
|
75
|
+
- Babel for JS transpilation
|
76
|
+
- Autoprefixer for CSS transpilation
|
45
77
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
78
|
+
<details><summary>Big ugly production-esque Rollup config</summary>
|
79
|
+
|
80
|
+
```js
|
81
|
+
import { builtinModules } from "module";
|
82
|
+
import path from "path";
|
83
|
+
import autoprefixer from "autoprefixer";
|
84
|
+
import babelPlugin from "@rollup/plugin-babel";
|
85
|
+
import commonjsPlugin from "@rollup/plugin-commonjs";
|
86
|
+
import jsonPlugin from "@rollup/plugin-json";
|
87
|
+
import markoPlugin from "@marko/rollup";
|
88
|
+
import nodeResolvePlugin from "@rollup/plugin-node-resolve";
|
89
|
+
import replacePlugin from "@rollup/plugin-replace";
|
90
|
+
import runPlugin from "@rollup/plugin-run";
|
91
|
+
import stylesPlugin from "rollup-plugin-styles";
|
92
|
+
import urlPlugin from "@rollup/plugin-url";
|
93
|
+
import pkg from "./package.json";
|
94
|
+
|
95
|
+
const __DEV__ = process.env.NODE_ENV === "development";
|
96
|
+
const __PROD__ = !__DEV__;
|
97
|
+
|
98
|
+
const isWatch = Boolean(process.env.ROLLUP_WATCH);
|
99
|
+
|
100
|
+
const publicPath = "/s/"; // Guess what character is only 5 bits under HPACK
|
101
|
+
const assetFileNames = "[name]-[hash][extname]";
|
102
|
+
|
103
|
+
const externalDependencies = [
|
104
|
+
...Object.keys(pkg.dependencies),
|
105
|
+
...builtinModules
|
106
|
+
];
|
107
|
+
|
108
|
+
process.env.SASS_PATH = "./:./node_modules";
|
109
|
+
|
110
|
+
export default (async () => [
|
111
|
+
compiler("server", {
|
112
|
+
input: "index.js",
|
113
|
+
output: {
|
114
|
+
dir: "built/server/",
|
115
|
+
assetFileNames: `../browser/${assetFileNames}`,
|
116
|
+
format: "cjs",
|
117
|
+
sourcemap: true
|
118
|
+
},
|
119
|
+
external: id =>
|
120
|
+
externalDependencies.some(
|
121
|
+
dependency => id === dependency || id.startsWith(dependency + "/")
|
122
|
+
),
|
123
|
+
plugins: [isWatch && runPlugin({ execArgv: ["--enable-source-maps"] })]
|
124
|
+
}),
|
125
|
+
|
126
|
+
compiler("browser", {
|
127
|
+
output: {
|
128
|
+
dir: "built/browser/",
|
129
|
+
chunkFileNames: __PROD__ ? "[name]-[hash].js" : null,
|
130
|
+
entryFileNames: __PROD__ ? "[name]-[hash].js" : null,
|
131
|
+
assetFileNames,
|
132
|
+
sourcemap: true,
|
133
|
+
sourcemapExcludeSources: __PROD__
|
134
|
+
},
|
135
|
+
plugins: [
|
136
|
+
stylesPlugin({
|
137
|
+
mode: "extract",
|
138
|
+
sourceMap: true,
|
139
|
+
config: {
|
140
|
+
target: "browserslist:css",
|
141
|
+
plugins: [autoprefixer({ env: "css" })]
|
142
|
+
},
|
143
|
+
minimize: __PROD__,
|
144
|
+
url: {
|
145
|
+
publicPath,
|
146
|
+
hash: assetFileNames
|
147
|
+
}
|
148
|
+
}),
|
149
|
+
__PROD__ && (await import("rollup-plugin-terser")).terser(),
|
150
|
+
__PROD__ &&
|
151
|
+
(await import("rollup-plugin-gzip")).default({
|
152
|
+
filter: /\.(?:js|css|svg|json|xml|txt)$/,
|
153
|
+
minSize: 1024,
|
154
|
+
gzipOptions: {
|
155
|
+
level: 9,
|
156
|
+
memLevel: 9
|
157
|
+
}
|
158
|
+
}),
|
159
|
+
__PROD__ &&
|
160
|
+
!isWatch &&
|
161
|
+
(await import("rollup-plugin-visualizer")).default(),
|
162
|
+
__PROD__ &&
|
163
|
+
!isWatch && {
|
164
|
+
name: "bundle-visualizer-location",
|
165
|
+
writeBundle() {
|
166
|
+
console.info(
|
167
|
+
`📊 Bundle visualizer at \x1b[4;36mfile://${path.join(
|
168
|
+
__dirname,
|
169
|
+
"../../",
|
170
|
+
bundleAnalyzerFilename
|
171
|
+
)}\x1b[0m`
|
172
|
+
);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
]
|
176
|
+
})
|
177
|
+
])();
|
178
|
+
|
179
|
+
function compiler(target, config) {
|
180
|
+
const isBrowser = target === "browser";
|
181
|
+
const browserslistEnv = isBrowser ? "js" : "server";
|
182
|
+
const babelConfig = {
|
183
|
+
comments: false,
|
184
|
+
browserslistEnv,
|
185
|
+
compact: false,
|
186
|
+
babelrc: false,
|
187
|
+
caller: { target }
|
188
|
+
};
|
189
|
+
if (isBrowser) {
|
190
|
+
babelConfig.presets = [
|
191
|
+
[
|
192
|
+
"@babel/preset-env",
|
193
|
+
{
|
194
|
+
browserslistEnv,
|
195
|
+
bugfixes: true
|
196
|
+
}
|
197
|
+
]
|
198
|
+
];
|
199
|
+
}
|
200
|
+
|
201
|
+
return {
|
202
|
+
...config,
|
203
|
+
preserveEntrySignatures: false,
|
204
|
+
plugins: [
|
205
|
+
markoPlugin[target]({ babelConfig }),
|
206
|
+
nodeResolvePlugin({
|
207
|
+
browser: isBrowser,
|
208
|
+
preferBuiltins: !isBrowser
|
209
|
+
}),
|
210
|
+
commonjsPlugin(),
|
211
|
+
replacePlugin({
|
212
|
+
preventAssignment: true,
|
213
|
+
values: { __DEV__, __PROD__ }
|
214
|
+
}),
|
215
|
+
babelPlugin({
|
216
|
+
babelHelpers: "bundled",
|
217
|
+
...babelConfig
|
218
|
+
}),
|
219
|
+
jsonPlugin(),
|
220
|
+
urlPlugin({
|
221
|
+
publicPath,
|
222
|
+
destDir: "built/browser/",
|
223
|
+
fileName: assetFileNames,
|
224
|
+
include: "**/*.{svg,png,jpg,jpeg}",
|
225
|
+
limit: 0, // Never Base64 & inline
|
226
|
+
emitFiles: !isBrowser
|
227
|
+
}),
|
228
|
+
...config.plugins
|
229
|
+
]
|
230
|
+
};
|
231
|
+
}
|
61
232
|
```
|
62
233
|
|
234
|
+
</details>
|
235
|
+
|
63
236
|
## `<rollup>` tag
|
64
237
|
|
65
|
-
|
238
|
+
Using both `.server()` and `.browser()` enables **the `<rollup>` tag**, which gives you complete control over how your app loads assets. That lets you do things like:
|
66
239
|
|
67
|
-
|
68
|
-
|
240
|
+
- [Critical CSS](https://web.dev/extract-critical-css/) for components as they write out within a kB budget, or [as components first appear on the page](https://jakearchibald.com/2016/link-in-body/#a-simpler-better-way), or any other style-loading mad science
|
241
|
+
- [`module`/`nomodule` scripts](https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/) for smaller bundles in modern browsers
|
242
|
+
- [Content-Security Policy `nonce`s](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) or [Subresource `integrity` hashes](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
|
243
|
+
- Anything a web page can do, really. You can even combine `<rollup>` with [the `serialize` option](#options.serialize) to be as fancy as you wanna be.
|
69
244
|
|
70
|
-
The
|
245
|
+
The `<rollup>` tag provides two [tag parameters](https://markojs.com/docs/syntax/#parameters):
|
246
|
+
|
247
|
+
1. `entry` is the generated `input` string that the `server` plugin gave to the `browser` plugin. You can use it to find the corresponding entry chunk from Rollup’s `output` (the next parameter).
|
248
|
+
|
249
|
+
2. `output` is an array of `AssetInfo | ChunkInfo` objects with most of [the data returned from Rollup's `generateBundle` hook](https://rollupjs.org/guide/en/#generatebundle). Some properties are omitted, like `code` and `map`, since they’re often too large to inline directly. However, each chunk also has a `size` property, to let you filter out empty chunks, inline code yourself below a certain size, or other delightful devilishness.
|
250
|
+
|
251
|
+
For example, using the `entry` name and properties of `output` items to load scripts:
|
71
252
|
|
72
253
|
```marko
|
73
254
|
<head>
|
74
255
|
<rollup|entry, output|>
|
75
256
|
$ const entryChunk = output.find(chunk => chunk.name === entry);
|
76
257
|
|
77
|
-
<if(entryChunk.size /*
|
258
|
+
<if(entryChunk.size /* only load non-empty JS entry points */)>
|
78
259
|
<for|fileName| of=entryChunk.imports>
|
79
|
-
<link rel="modulepreload" href=fileName/>
|
260
|
+
<link rel="modulepreload" href=fileName />
|
80
261
|
</for>
|
81
262
|
|
82
|
-
<script async type="module" src=entryChunk.fileName
|
263
|
+
<script async type="module" src=entryChunk.fileName></script>
|
83
264
|
</if>
|
84
265
|
</rollup>
|
85
266
|
</head>
|
86
267
|
```
|
87
268
|
|
88
|
-
|
269
|
+
> **Note**: It’s up to you to transform the chunk data (also called the **manifest**) into `<link>`s, `<script>`s, and other HTML to load assets. Opting into complete control means we can’t do any of it for you.
|
89
270
|
|
90
|
-
If your
|
271
|
+
If your Rollup `browser` config contains multiple `output` options, or you have multiple `browser` configs, every `output`’s chunk is passed to the `<rollup>` tag.
|
91
272
|
|
92
|
-
For example if you have
|
273
|
+
For example, if you have both `esm` and `iife` build outputs configured:
|
93
274
|
|
94
|
-
```
|
275
|
+
```js
|
95
276
|
{
|
96
|
-
plugins: [
|
97
|
-
marko.browser()
|
98
|
-
...
|
99
|
-
],
|
100
277
|
output: [
|
101
|
-
{ dir:
|
102
|
-
{ dir:
|
103
|
-
]
|
278
|
+
{ dir: "dist/iife", format: "iife" },
|
279
|
+
{ dir: "dist/esm", format: "esm" }
|
280
|
+
];
|
104
281
|
}
|
105
282
|
```
|
106
283
|
|
107
|
-
|
284
|
+
…you could cross-reference assets from both…
|
108
285
|
|
109
286
|
```marko
|
110
|
-
<
|
111
|
-
|
112
|
-
|
113
|
-
$ const esmEntryChunk = esmOutput.find(chunk => chunk.name === entry);
|
287
|
+
<rollup|entry, iifeOutput, esmOutput|>
|
288
|
+
$ const iifeEntryChunk = iifeOutput.find(chunk => chunk.name === entry);
|
289
|
+
$ const esmEntryChunk = esmOutput.find(chunk => chunk.name === entry);
|
114
290
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
</head>
|
291
|
+
<script src=esmEntryChunk.fileName type="module" async></script>
|
292
|
+
<script src=iifeEntryChunk.fileName nomodule async></script>
|
293
|
+
</rollup>
|
119
294
|
```
|
120
295
|
|
121
|
-
and
|
122
|
-
|
123
|
-
# Top level components
|
124
|
-
|
125
|
-
Marko was designed to send as little JavaScript to the browser as possible. One of the ways we do this is by automatically determining which templates in your app should be shipped to the browser. When rendering a template on the server, it is only necessary to bundle the styles and interactive components rendered by that template.
|
126
|
-
|
127
|
-
To send the minimal amount of Marko templates to the browser you can provide a Marko template directly as the `input`.
|
128
|
-
This will also automatically invoke code to initialize the components in the browser, so there is no need to call
|
129
|
-
`template.render` yourself in the browser.
|
130
|
-
|
131
|
-
> Note: if you are using _linked_ plugins then the server plugin will automatically tell the browser compiler which Marko templates to load.
|
132
|
-
|
133
|
-
```js
|
134
|
-
export default {
|
135
|
-
input: "./my-marko-page.marko",
|
136
|
-
plugins: [
|
137
|
-
marko.browser(),
|
138
|
-
...
|
139
|
-
],
|
140
|
-
...
|
141
|
-
}
|
142
|
-
```
|
296
|
+
…and _boom:_ you now have [a `module`/`nomodule` setup](https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/).
|
143
297
|
|
144
298
|
## Options
|
145
299
|
|
146
|
-
|
300
|
+
### `options.babelConfig`
|
147
301
|
|
148
|
-
|
302
|
+
Both the `.server()` and `.browser()` plugins accept this option.
|
149
303
|
|
150
|
-
You can manually override the Babel configuration
|
304
|
+
You can manually override the builtin Babel configuration by passing a `babelConfig` object. By default, [Babel’s regular config file resolution](https://babeljs.io/docs/en/config-files) will be used.
|
151
305
|
|
152
|
-
```
|
306
|
+
```js
|
153
307
|
marko.browser({
|
154
308
|
babelConfig: {
|
155
309
|
presets: ["@babel/preset-env"]
|
@@ -157,42 +311,40 @@ marko.browser({
|
|
157
311
|
});
|
158
312
|
```
|
159
313
|
|
160
|
-
### options.runtimeId
|
314
|
+
### `options.runtimeId`
|
315
|
+
|
316
|
+
Both the `.server()` and `.browser()` plugins accept this option. In fact, you _really_ want to use it with both simultaneously.
|
161
317
|
|
162
|
-
In some cases you may want to embed multiple isolated copies of Marko on the page.
|
318
|
+
In some cases, you may want to embed multiple isolated copies of Marko on the page. (If you can’t think of why, then don’t worry about this option.)
|
163
319
|
|
164
|
-
|
320
|
+
Since Marko uses some `window` properties to initialize, multiple instances can cause issues. For example, by default Marko checks `window.$components` for server-rendered hydration. Usually you can change these `window` properties by [rendering with `{ $global: { runtimeId: "MY_MARKO_RUNTIME_ID" } }` as input](https://markojs.com/docs/rendering/#global-data) on the server, but since `@marko/rollup` usually writes the autoinitialization code for you, instead this plugin exposes a `runtimeId` option to automatically set `$global.runtimeId` to initialize properly in the browser:
|
165
321
|
|
166
322
|
```js
|
167
323
|
const runtimeId = "MY_MARKO_RUNTIME_ID";
|
168
|
-
// Make
|
324
|
+
// Make the `runtimeId` the same across `server` and `browser`, or it’ll error!
|
169
325
|
marko.server({ runtimeId });
|
170
326
|
marko.browser({ runtimeId });
|
171
327
|
```
|
172
328
|
|
173
|
-
### options.serialize
|
329
|
+
### `options.serialize`
|
174
330
|
|
175
|
-
This option is only available for the
|
176
|
-
|
331
|
+
This option is only available for the `.browser()` plugin. It lets you inspect and transform the `output` chunks before they’re passed to [the `<rollup>` tag](#rollup-tag).
|
332
|
+
|
333
|
+
For example, if you _did_ want to include the `code` property from the Rollup chunk — say, to inline code small enough that it’s not worth the overhead of an HTTP request, you’d try something like the following:
|
177
334
|
|
178
335
|
```js
|
179
336
|
marko.browser({
|
180
337
|
serialize(output) {
|
181
|
-
return output.map(
|
182
|
-
|
183
|
-
? {
|
184
|
-
type: "asset",
|
185
|
-
fileName: chunk.fileName
|
186
|
-
}
|
338
|
+
return output.map(({ type, fileName, isEntry, code }) =>
|
339
|
+
type === "asset"
|
340
|
+
? { type, fileName }
|
187
341
|
: {
|
188
|
-
type
|
189
|
-
name
|
190
|
-
isEntry
|
191
|
-
fileName
|
192
|
-
code
|
193
|
-
|
194
|
-
? chunk.code
|
195
|
-
: undefined // only inline small code chunks
|
342
|
+
type,
|
343
|
+
name,
|
344
|
+
isEntry,
|
345
|
+
fileName,
|
346
|
+
// only inline code chunks below 1kB
|
347
|
+
inline: code.trim().length < 1024 && code
|
196
348
|
}
|
197
349
|
);
|
198
350
|
}
|
@@ -10,10 +10,16 @@
|
|
10
10
|
|
11
11
|
- Some software doesn’t support HTTP/2 or higher “upstream” connections at all or very well — if your Node server uses HTTP/2, you may need to downgrade.
|
12
12
|
|
13
|
-
- Automatic gzip/brotli compression may have their buffer sizes set too high; you can tune their buffers to be smaller for faster streaming in exchange for slightly worse compression.
|
14
|
-
|
15
13
|
- Check if “upstream” connections are `keep-alive`: overhead from closing and reopening connections may delay responses.
|
16
14
|
|
15
|
+
- For typical modern webpage filesizes, the following bullet points probably won’t matter. But if you want to stream **small chunks of data with the lowest latency**, investigate these sources of buffering:
|
16
|
+
|
17
|
+
- Automatic gzip/brotli compression may have their buffer sizes set too high; you can tune their buffers to be smaller for faster streaming in exchange for slightly worse compression.
|
18
|
+
|
19
|
+
- You can [tune HTTPS record sizes for lower latency, as described in High Performance Browser Networking](https://hpbn.co/transport-layer-security-tls/#optimize-tls-record-size).
|
20
|
+
|
21
|
+
- Turning off MIME sniffing with [the `X-Content-Type-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) header eliminates browser buffering at the very beginning of HTTP responses
|
22
|
+
|
17
23
|
### NGiNX
|
18
24
|
|
19
25
|
Most of NGiNX’s relevant parameters are inside [its builtin `http_proxy` module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering):
|
package/package.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "marko",
|
3
|
-
"version": "5.22.
|
3
|
+
"version": "5.22.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.23.
|
8
|
-
"@marko/translator-default": "^5.22.
|
7
|
+
"@marko/compiler": "^5.23.3",
|
8
|
+
"@marko/translator-default": "^5.22.3",
|
9
9
|
"app-module-path": "^2.2.0",
|
10
10
|
"argly": "^1.2.0",
|
11
11
|
"browser-refresh-client": "1.1.4",
|
@@ -71,5 +71,6 @@
|
|
71
71
|
"index-browser.marko",
|
72
72
|
"index.js",
|
73
73
|
"node-require.js"
|
74
|
-
]
|
74
|
+
],
|
75
|
+
"types": "index.d.ts"
|
75
76
|
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* @template T
|
3
|
+
* @typedef {{
|
4
|
+
* value?: T;
|
5
|
+
* then?: Marko.Body<[Awaited<T>], void>;
|
6
|
+
* catch?: Marko.Body<[unknown], void>;
|
7
|
+
* placeholder?: Marko.Body<[], void>;
|
8
|
+
* client-reorder?: boolean;
|
9
|
+
* name?: string;
|
10
|
+
* timeout?: number;
|
11
|
+
* show-after?: string;
|
12
|
+
* }} Input
|
13
|
+
*/
|
@@ -27,7 +27,13 @@ StringWriter.prototype = {
|
|
27
27
|
|
28
28
|
merge: function (otherWriter) {
|
29
29
|
this._content += otherWriter._content;
|
30
|
-
|
30
|
+
|
31
|
+
if (otherWriter._scripts) {
|
32
|
+
this._scripts = this._scripts
|
33
|
+
? this._scripts + ";" + otherWriter._scripts
|
34
|
+
: otherWriter._scripts;
|
35
|
+
}
|
36
|
+
|
31
37
|
if (otherWriter._data) {
|
32
38
|
if (this._data) {
|
33
39
|
for (const key in otherWriter._data) {
|