1 | # What's New in Marko v4
|
2 |
|
3 | A lot of exciting improvements were made as part of the Mark v4 release.
|
4 |
|
5 | ## Notable Changes and Improvements
|
6 |
|
7 | ### Single file components ([#399](https://github.com/marko-js/marko/issues/399))
|
8 |
|
9 | Marko now supports combining HTML, rendering logic, client-side behavior and styling into a single file component.
|
10 |
|
11 | _src/components/click-count/index.marko_
|
12 |
|
13 | ```marko
|
14 | class {
|
15 | onCreate() {
|
16 | this.state = {
|
17 | count: 0
|
18 | }
|
19 | }
|
20 |
|
21 | onInput(input) {
|
22 | this.state.count = input.value || 0;
|
23 | }
|
24 |
|
25 | increment() {
|
26 | this.state.count++;
|
27 | }
|
28 | }
|
29 |
|
30 | style.less {
|
31 | .count {
|
32 | color: #09c;
|
33 | }
|
34 | .button {
|
35 | background: #fff;
|
36 | }
|
37 | }
|
38 |
|
39 | <div>
|
40 | <span class="count">${state.count}</span>
|
41 | <button class="button" on-click('increment')>
|
42 | increment count
|
43 | </button>
|
44 | </div>
|
45 | ```
|
46 |
|
47 | You can easily `require`/`import` a single file component and interact with it using the exported JavaScript API:
|
48 |
|
49 | ```js
|
50 | var clickCount = require("./src/components/click-count");
|
51 |
|
52 | var component = clickCount
|
53 | .renderSync({
|
54 | value: 10
|
55 | })
|
56 | .appendTo(document.body)
|
57 | .getComponent();
|
58 |
|
59 | component.increment();
|
60 | ```
|
61 |
|
62 | Of course, a single file component can also be embedded in another template as a custom tag:
|
63 |
|
64 | ```marko
|
65 | <div>
|
66 | <click-count value=10 />
|
67 | </div>
|
68 | ```
|
69 |
|
70 | ### Virtual DOM support ([#366](https://github.com/marko-js/marko/issues/366))
|
71 |
|
72 | 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.
|
73 |
|
74 | 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:
|
75 |
|
76 | _Compiled for HTML output (server-side):_
|
77 |
|
78 | ```javascript
|
79 | function render(data, out) {
|
80 | var colors = data.colors;
|
81 |
|
82 | if (colors && colors.length) {
|
83 | out.w("<ul>");
|
84 |
|
85 | marko_forEach(colors, function(color) {
|
86 | out.w('<li class="color">' + marko_escapeXml(color) + "</li>");
|
87 | });
|
88 |
|
89 | out.w("</ul>");
|
90 | } else {
|
91 | out.w("<div>No colors!</div>");
|
92 | }
|
93 | }
|
94 | ```
|
95 |
|
96 | _Compiled for VDOM output (browser-side):_
|
97 |
|
98 | ```javascript
|
99 | var marko_attrs0 = {
|
100 | class: "color"
|
101 | },
|
102 | marko_node0 = marko_createElement("DIV", null, 1, marko_const_nextId()).t(
|
103 | "No colors!"
|
104 | );
|
105 |
|
106 | function render(data, out) {
|
107 | var colors = data.colors;
|
108 |
|
109 | if (colors && colors.length) {
|
110 | out.be("UL");
|
111 |
|
112 | marko_forEach(colors, function(color) {
|
113 | out.e("LI", marko_attrs0, 1).t(marko_str(color));
|
114 | });
|
115 |
|
116 | out.ee();
|
117 | } else {
|
118 | out.n(marko_node0);
|
119 | }
|
120 | }
|
121 | ```
|
122 |
|
123 | The VDOM output allows optimizations that were previously not possible:
|
124 |
|
125 | - Static subtrees are pulled into variables that are only initialized once and reused for every render
|
126 | - Static attributes that are on dynamic elements are pulled out to static variables
|
127 | - Diffing is skipped when comparing static subtrees
|
128 | - Diffing is skipped when comparing static attributes
|
129 |
|
130 | Our benchmarks show a significant improvement in rendering time and we are consistently outperforming React/Preact/Inferno, Vue and other UI libraries.
|
131 |
|
132 | ### Merge in Marko Widgets ([#390](https://github.com/marko-js/marko/issues/390))
|
133 |
|
134 | 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.
|
135 |
|
136 | You will no longer need to install `marko-widgets` as an external library, and there is more cohesion between the templates and components/components.
|
137 |
|
138 | ### Improved component lifecycle methods ([#396](https://github.com/marko-js/marko/issues/396))
|
139 |
|
140 | - `getInitialState()` ➔ `onInput(input)`
|
141 | - `getComponentConfig()` ➔ `onInput(input)`
|
142 | - `init(config)` ➔ `onMount()`
|
143 | - `getTemplateData(input, state)` ➔ (no longer needed)
|
144 | - `getInitialProps(input)` ➔ (no longer needed)
|
145 |
|
146 | ```js
|
147 | class {
|
148 | onCreate(input) {
|
149 | this.state = {
|
150 | count: 0
|
151 | }
|
152 | this.initialCount = 0;
|
153 | }
|
154 |
|
155 | onInput(input) {
|
156 | if (input.count) {
|
157 | // if the parent component passes a value
|
158 | // for count, we'll reset our state to that
|
159 | // value
|
160 | this.state.count = input.count;
|
161 | this.initialCount = input.count;
|
162 | }
|
163 | }
|
164 |
|
165 | onRender(out) {
|
166 | // Called for every render. This component
|
167 | // may or may not be mounted.
|
168 | // During render we have access to the `out`.
|
169 | console.log('The template is about to be rendered!');
|
170 | }
|
171 |
|
172 | onMount() {
|
173 | console.log('The component has mounted!');
|
174 |
|
175 | console.log('Count: ' + this.state.count);
|
176 | console.log('Initial count: ' + this.initialCount);
|
177 | }
|
178 |
|
179 | onUpdate() {
|
180 | console.log('The DOM has been updated!');
|
181 | }
|
182 |
|
183 | onDestroy() {
|
184 | console.log('The component is about to be destroyed :(')
|
185 | }
|
186 |
|
187 | // Helper methods:
|
188 |
|
189 | reset() {
|
190 | this.state.count = this.initialCount;
|
191 | }
|
192 |
|
193 | increment() {
|
194 | this.state.count++;
|
195 | }
|
196 |
|
197 | decrement() {
|
198 | this.state.count--;
|
199 | }
|
200 | }
|
201 | ```
|
202 |
|
203 | ### Automatically watch component state object for changes ([#406](https://github.com/marko-js/marko/issues/406))
|
204 |
|
205 | **Old:**
|
206 |
|
207 | ```js
|
208 | {
|
209 | getInitialState(input) {
|
210 | return {
|
211 | count: input.count || 0
|
212 | };
|
213 | }
|
214 | increment() {
|
215 | this.setState('count', this.state.count+1);
|
216 | }
|
217 | }
|
218 | ```
|
219 |
|
220 | **New:**
|
221 |
|
222 | ```js
|
223 | {
|
224 | onInput(input) {
|
225 | this.state = {
|
226 | count: input.count || 0
|
227 | };
|
228 | }
|
229 | increment() {
|
230 | this.state.count++;
|
231 | }
|
232 | }
|
233 | ```
|
234 |
|
235 | In addition, the default state can now be declared:
|
236 |
|
237 | ```js
|
238 | {
|
239 | onCreate() {
|
240 | this.state = {
|
241 | count: 0
|
242 | };
|
243 | }
|
244 |
|
245 | onInput(input) {
|
246 | this.state = {
|
247 | count: input.count
|
248 | };
|
249 | }
|
250 |
|
251 | increment() {
|
252 | this.state.count++;
|
253 | }
|
254 | }
|
255 | ```
|
256 |
|
257 | ### Consistent rendering API ([#415](https://github.com/marko-js/marko/issues/415))
|
258 |
|
259 | In Marko v3, UI components exported an API that included DOM insertion methods while simple templates only exported an API for rendering to a string/stream. With Marko v4, simple templates and UI components now export the exact same rendering API.
|
260 |
|
261 | ```js
|
262 | // Append to an existing DOM node:
|
263 | require("./template.marko")
|
264 | .renderSync({ name: "Frank " })
|
265 | .appendTo(document.body);
|
266 |
|
267 | // Replace an existing DOM node:
|
268 | require("./template.marko")
|
269 | .renderSync({ name: "Frank " })
|
270 | .replace(document.getElementById("foo"));
|
271 | ```
|
272 |
|
273 | ### Less Boilerplate
|
274 |
|
275 | - Removed: `w-bind`
|
276 | - Removed: `w-extend`
|
277 | - Removed: `require('marko-widgets').defineComponent(...)`
|
278 | - Removed: `require('marko-widgets').defineWidget(...)`
|
279 | - Removed: `require('marko-widgets').defineRenderer(...)`
|
280 | - Removed: `w-body` (use `<include()>` instead)
|
281 | - `w-on*="handleSomeEvent"` --> `on*('handleSomeEvent')`
|
282 | - `w-id` --> `key`
|
283 | - `w-for` --> `for-key`
|
284 | - `w-preserve` --> `no-update`
|
285 | - `class="foo" w-preserve-attrs="class"` --> `class:no-update="foo"`
|
286 |
|
287 | Some of these things are described in more detail later in this document.
|
288 |
|
289 | ### Embedded JavaScript blocks
|
290 |
|
291 | **Old:**
|
292 |
|
293 | ```marko
|
294 | <var name="Frank"/>
|
295 | <assign name="John"/>
|
296 | <invoke console.log(name)/>
|
297 | ```
|
298 |
|
299 | **New:**
|
300 |
|
301 | ```marko
|
302 | $ var name='Frank';
|
303 | $ name='John';
|
304 | $ console.log(name);
|
305 | ```
|
306 |
|
307 | Alternatively:
|
308 |
|
309 | ```marko
|
310 | $ {
|
311 | var name='Frank';
|
312 | name='John';
|
313 | console.log(name);
|
314 | }
|
315 | ```
|
316 |
|
317 | 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:
|
318 |
|
319 | ```marko
|
320 | <div.hello>
|
321 | <for(name in names)>
|
322 | $ name = name.toUpperCase();
|
323 | Hello ${name}!
|
324 | </for>
|
325 | </div>
|
326 | ```
|
327 |
|
328 | Static JavaScript blocks (for JavaScript that should only be executed once when the template is first loaded) are also supported:
|
329 |
|
330 | **Old:**
|
331 |
|
332 | ```marko
|
333 | <script marko-init>
|
334 | function formatName(person) {
|
335 | return person.firstName + ' ' + person.lastName;
|
336 | }
|
337 | function isTeenager(person) {
|
338 | return person.age > 12 && person.age < 20;
|
339 | }
|
340 | </script>
|
341 | <div>
|
342 | Hello ${formatName(input.person)}!
|
343 | Teenager?: ${isTeenager(input.person) ? 'Yes' : 'No'}
|
344 | </div>
|
345 | ```
|
346 |
|
347 | **New:**
|
348 |
|
349 | ```marko
|
350 | static function formatName(person) {
|
351 | return person.firstName + ' ' + person.lastName;
|
352 | }
|
353 |
|
354 | static function isTeenager(person) {
|
355 | return person.age > 12 && person.age < 20;
|
356 | }
|
357 | <div>
|
358 | Hello ${formatName(input.person)}!
|
359 | Teenager?: ${isTeenager(input.person) ? 'Yes' : 'No'}
|
360 | </div>
|
361 | ```
|
362 |
|
363 | Alternatively:
|
364 |
|
365 | ```marko
|
366 | static {
|
367 | function formatName(person) {
|
368 | return person.firstName + ' ' + person.lastName;
|
369 | }
|
370 |
|
371 | function isTeenager(person) {
|
372 | return person.age > 12 && person.age < 20;
|
373 | }
|
374 | }
|
375 | <div>
|
376 | Hello ${formatName(input.person)}!
|
377 | Teenager?: ${isTeenager(input.person) ? 'Yes' : 'No'}
|
378 | </div>
|
379 | ```
|
380 |
|
381 | ### Template variables
|
382 |
|
383 | - `data` --> `input` - References the input object (should be treated as immutable)
|
384 | - Introduced `state` - References the components raw state object (for components only)
|
385 | - Introduced `component` - References the component instance (for components only)
|
386 |
|
387 | ## Other Improvements
|
388 |
|
389 | ### Only diff attributes that are rendered by Marko ([#417](https://github.com/marko-js/marko/issues/417))
|
390 |
|
391 | Previously, when diffing the DOM, all of the attributes on a real HTML element node were diffed with all of the attributes on a newly rendered HTML element node. This posed a problem when using Marko with third party libraries, such as animation libraries, that added HTML attributes that should have been left alone. The proposed workaround was to add the `w-preserve-attrs` attribute wherever needed.
|
392 |
|
393 | In Marko v4, only the attributes rendered by Marko are ever modified by Marko. Any attributes added by third-party libraries are simply ignored.
|
394 |
|
395 | ### Allow multiple top-level DOM elements to be bound ([#393](https://github.com/marko-js/marko/issues/393))
|
396 |
|
397 | **Old:**
|
398 |
|
399 | ```marko
|
400 | <div w-bind>
|
401 | <h1>The current count is ${data.count}</h1>
|
402 | <button onClick('incrementCount')>Increment Count</button>
|
403 | </div>
|
404 | ```
|
405 |
|
406 | **New:**
|
407 |
|
408 | ```marko
|
409 | <h1>The current count is ${input.count}</h1>
|
410 | <button onClick('incrementCount')>Increment Count</button>
|
411 | ```
|
412 |
|
413 | ### Template as entry point for UI components ([#416](https://github.com/marko-js/marko/issues/416))
|
414 |
|
415 | **Old:**
|
416 |
|
417 | `index.js`
|
418 |
|
419 | ```js
|
420 | module.exports = require('marko-widgets').defineComponent({
|
421 | template: require('./template.marko'),
|
422 | ...
|
423 | });
|
424 | ```
|
425 |
|
426 | `template.marko`
|
427 |
|
428 | ```marko
|
429 | <div w-bind>
|
430 | ...
|
431 | </div>
|
432 | ```
|
433 |
|
434 | **New:**
|
435 |
|
436 | `component.js`
|
437 |
|
438 | ```js
|
439 | module.exports = {
|
440 | ...
|
441 | };
|
442 | ```
|
443 |
|
444 | `index.marko`
|
445 |
|
446 | ```marko
|
447 | <div>
|
448 | ...
|
449 | </div>
|
450 | ```
|
451 |
|
452 | > The compiled template now exports the component
|
453 |
|
454 | ### Allow event handler attribute to bind additional arguments ([#401](https://github.com/marko-js/marko/issues/401))
|
455 |
|
456 | **Old:**
|
457 |
|
458 | ```marko
|
459 | <ul for(color in colors)>
|
460 | <li w-onClick="handleColorClick" data-color=color>${color}</li>
|
461 | </ul>
|
462 | ```
|
463 |
|
464 | ```js
|
465 | handleColorClick(event, el) {
|
466 | console.log(el.getAttribute('data-color'), 'was clicked');
|
467 | }
|
468 | ```
|
469 |
|
470 | **New:**
|
471 |
|
472 | ```marko
|
473 | class {
|
474 | handleColorClick(color, event, el) {
|
475 | console.log(color, 'was clicked');
|
476 | }
|
477 | }
|
478 |
|
479 | <ul for(color in colors)>
|
480 | <li onClick('handleColorClick', color)>${color}</li>
|
481 | </ul>
|
482 | ```
|
483 |
|
484 | NOTE: `w-on*` has been deprecated. See: [Deprecate `w-on*` in favor of `on*()`](#deprecate-w-on)
|
485 |
|
486 | ### Introduce the `<import>` tag ([#404](https://github.com/marko-js/marko/issues/404))
|
487 |
|
488 | Marko v4 introduces ES6 style imports for importing other JavaScript modules:
|
489 |
|
490 | **Old:**
|
491 |
|
492 | ```marko
|
493 | <script marko-init>
|
494 | var helpers = require('./helpers');
|
495 | </script>
|
496 | <div>Total: ${helpers.formatCurrency(data.total)}</div>
|
497 | ```
|
498 |
|
499 | **New:**
|
500 |
|
501 | ```marko
|
502 | import helpers from "./helpers"
|
503 | <div>Total: ${helpers.formatCurrency(data.total)}</div>
|
504 | ```
|
505 |
|
506 | The full ES6 import syntax is supported:
|
507 |
|
508 | ```marko
|
509 | import { formatCurrency } from "./helpers"
|
510 | <div>Total: ${formatCurrency(data.total)}</div>
|
511 | ```
|
512 |
|
513 | ### Allow dynamic custom tags/components to be used with `<include>` ([#139](https://github.com/marko-js/marko/issues/139))
|
514 |
|
515 | **Old:**
|
516 |
|
517 | ```marko
|
518 | <invoke data.myComponent.renderer({name: 'Frank'}, out)/>
|
519 | ```
|
520 |
|
521 | **New:**
|
522 |
|
523 | ```marko
|
524 | <include(input.myComponent) name='Frank' />
|
525 | ```
|
526 |
|
527 | or
|
528 |
|
529 | ```marko
|
530 | <include(input.myComponent, {name: 'Frank'}) />
|
531 | ```
|
532 |
|
533 | ### Single file components outside of a directory are not automatically discovered
|
534 |
|
535 | The following are equivalent:
|
536 |
|
537 | Component directory:
|
538 |
|
539 | ```
|
540 | components/hello/index.marko
|
541 | ```
|
542 |
|
543 | Component file:
|
544 |
|
545 | ```
|
546 | components/hello.marko
|
547 | ```
|
548 |
|
549 | ### Supporting files for UI components
|
550 |
|
551 | The following are equivalent:
|
552 |
|
553 | Component directory:
|
554 |
|
555 | ```
|
556 | components/hello/
|
557 | compnent.js
|
558 | index.marko
|
559 | style.less
|
560 | ```
|
561 |
|
562 | Component file:
|
563 |
|
564 | ```bash
|
565 | components/
|
566 | hello.component.js
|
567 | hello.marko
|
568 | hello.style.less
|
569 | ```
|
570 |
|
571 | ### Split component
|
572 |
|
573 | Marko v4 improves how split components are supported. Split component allow only the client-side logic to be sent to the browser if the rendering logic (and view template) are not needed in the browser:
|
574 |
|
575 | ```
|
576 | components/hello/
|
577 | component.js # Required to render
|
578 | component-browser.js # Required to handle events, etc.
|
579 | index.marko # Required to render
|
580 | ```
|
581 |
|
582 | ### Introduce `state` as a local variable ([#400](https://github.com/marko-js/marko/issues/400))
|
583 |
|
584 | **Old:**
|
585 |
|
586 | `component.js`
|
587 |
|
588 | ```js
|
589 | {
|
590 | getInitialState(input) {
|
591 | return {
|
592 | name: input.name,
|
593 | birthday: input.birthday
|
594 | }
|
595 | },
|
596 | getTemplateData(state, input) {
|
597 | return {
|
598 | name: state.name,
|
599 | age: calculateAge(state.birthday)
|
600 | }
|
601 | },
|
602 | ...
|
603 | }
|
604 | ```
|
605 |
|
606 | `template.marko`
|
607 |
|
608 | ```marko
|
609 | <div>
|
610 | Hello ${data.name}! You are ${data.age} year(s) old.
|
611 | </div>
|
612 | ```
|
613 |
|
614 | **New:**
|
615 |
|
616 | `component.js`
|
617 |
|
618 | ```js
|
619 | {
|
620 | onInput(input) {
|
621 | // `this.state` will be available as the `state` variable
|
622 | // in the template.
|
623 | this.state = {
|
624 | name: input.name,
|
625 | birthday: input.birthday
|
626 | };
|
627 | }
|
628 | ...
|
629 | }
|
630 | ```
|
631 |
|
632 | `template.marko`
|
633 |
|
634 | ```marko
|
635 | $ var age = calculateAge(state.birthday);
|
636 | <div>
|
637 | Hello ${state.name}! You are ${age} year(s) old.
|
638 | </div>
|
639 | ```
|
640 |
|
641 | ### Make output of render `Promise`-compatible ([#251](https://github.com/marko-js/marko/issues/251))
|
642 |
|
643 | **Old:**
|
644 |
|
645 | ```js
|
646 | template.render({}, function(err, html, out) {});
|
647 | ```
|
648 |
|
649 | **New:**
|
650 |
|
651 | ```js
|
652 | template
|
653 | .render({})
|
654 | .then(function(result) {})
|
655 | .catch(function(err) {});
|
656 |
|
657 | // render() can now be used with async/await
|
658 | var out = await template.render({});
|
659 | out.appendTo(document.body);
|
660 | ```
|
661 |
|
662 | NOTE: callback/events still work as well
|
663 |
|
664 | ### Make `<await-reorderer/>` optional ([#410](https://github.com/marko-js/marko/issues/410))
|
665 |
|
666 | **Old:**
|
667 |
|
668 | ```marko
|
669 | <html>
|
670 | ...
|
671 | <body>
|
672 | ...
|
673 | <await-reorderer/>
|
674 | </body>
|
675 | </html>
|
676 | ```
|
677 |
|
678 | **New:**
|
679 |
|
680 | ```marko
|
681 | <html>
|
682 | ...
|
683 | <body>
|
684 | ...
|
685 | </body>
|
686 | </html>
|
687 | ```
|
688 |
|
689 | _Automatically inserted before `</body>`_
|
690 |
|
691 | ### Allow multiple extensions when installing the Node.js require hook ([#407](https://github.com/marko-js/marko/issues/407))
|
692 |
|
693 | **Old:**
|
694 |
|
695 | ```js
|
696 | require("marko/node-require").install({
|
697 | extension: ".marko"
|
698 | });
|
699 | ```
|
700 |
|
701 | **New:**
|
702 |
|
703 | ```js
|
704 | require("marko/node-require").install({
|
705 | extensions: [".marko", ".marko.xml", ".html"]
|
706 | });
|
707 | ```
|
708 |
|
709 | ### Auto hot reload for any extensions provided when installing the Node.js require hook ([#363](https://github.com/marko-js/marko/issues/363))
|
710 |
|
711 | Hot reload any extensions that were registered via `require('marko/node-require').install()`.
|
712 |
|
713 | ### Allow spaces around attributes ([#403](https://github.com/marko-js/marko/issues/403))
|
714 |
|
715 | **Old:**
|
716 |
|
717 | ```marko
|
718 | var className="foo"
|
719 | <div class=className/>
|
720 | ```
|
721 |
|
722 | **New:**
|
723 |
|
724 | ```marko
|
725 | $ var className = "foo"
|
726 | <div class = className/>
|
727 | ```
|
728 |
|
729 | > NOTE: spaces are **allowed**, not required
|
730 |
|
731 | ### Allow compile-time transformers to be registered at the template level ([#408](https://github.com/marko-js/marko/issues/408))
|
732 |
|
733 | `marko.json`
|
734 |
|
735 | ```json
|
736 | {
|
737 | "transformer": "./my-transformer.js"
|
738 | }
|
739 | ```
|
740 |
|
741 | `my-transformer.js`
|
742 |
|
743 | ```js
|
744 | module.exports = function transform(rootNode, context) {
|
745 | // ...
|
746 | };
|
747 | ```
|
748 |
|
749 | [see commit](https://github.com/marko-js/marko/commit/a35e6bdbc3fe6e7f4e92fb377c435e29ab3d6e33)
|
750 |
|
751 | ### Allow regular expression for an HTML attribute value ([#386](https://github.com/marko-js/marko/issues/386))
|
752 |
|
753 | **Old:**
|
754 |
|
755 | ```marko
|
756 | <!-- escaped backslash (\) since strings are parsed as JS values -->
|
757 | <input type="text" pattern="\\w{2,20}" />
|
758 | ```
|
759 |
|
760 | **New:**
|
761 |
|
762 | ```marko
|
763 | <!-- just use a regex -->
|
764 | <input type="text" pattern=/\w{2,20}/ />
|
765 | ```
|
766 |
|
767 | ## Deprecations
|
768 |
|
769 | 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).
|
770 |
|
771 | Additionally, [`marko-migrate`](https://github.com/marko-js/marko-migrate) will be updated to handle many of the deprecations described below.
|
772 |
|
773 | ### Deprecate `<script marko-init>` and replace with `static` section ([#397](https://github.com/marko-js/marko/issues/397))
|
774 |
|
775 | **Old:**
|
776 |
|
777 | ```marko
|
778 | <script marko-init>
|
779 | var format = require('format');
|
780 | </script>
|
781 | <var name="World"/>
|
782 | <div>Hello ${format(name)}</div>
|
783 | ```
|
784 |
|
785 | **New:**
|
786 |
|
787 | ```marko
|
788 | static var format=require('format')
|
789 | $ var name='World'
|
790 | <div>Hello ${format(name)}</div>
|
791 | ```
|
792 |
|
793 | ### Deprecate `<var>`, `<assign>` and `<invoke>`
|
794 |
|
795 | Use embedded JavaScript blocks instead
|
796 |
|
797 | ### Deprecate `w-bind` ([#394](https://github.com/marko-js/marko/issues/394), [#395](https://github.com/marko-js/marko/issues/395))
|
798 |
|
799 | **Old:**
|
800 |
|
801 | ```marko
|
802 | <div w-bind>
|
803 | ...
|
804 | </div>
|
805 | ```
|
806 |
|
807 | **New:**
|
808 |
|
809 | ```marko
|
810 | <div>
|
811 | ...
|
812 | </div>
|
813 | ```
|
814 |
|
815 | (Automatic binding to `./component.js`)
|
816 |
|
817 | ### Deprecate `widget-types` ([#514](https://github.com/marko-js/marko/issues/514))
|
818 |
|
819 | **Old:**
|
820 |
|
821 | ```marko
|
822 | <widget-types default="./component" mobile="./component-mobile"/>
|
823 |
|
824 | <div w-bind=(data.isMobile ? 'default' : 'mobile')>
|
825 | ...
|
826 | </div>
|
827 | ```
|
828 |
|
829 | `widget-types` should no longer be used. Instead, the JavaScript module bundler should handle the remapping if needed.
|
830 |
|
831 | ### Deprecate `w-id` and `w-for` in favor of `key` and `for-key` ([#394](https://github.com/marko-js/marko/issues/394))
|
832 |
|
833 | The `w-id` attribute was used to obtain references using `this.getEl(refId)`. `w-id` has been replaced with the `key` attribute:
|
834 |
|
835 | **Old:**
|
836 |
|
837 | ```marko
|
838 | <input type="text" w-id="nameInput" />
|
839 | ```
|
840 |
|
841 | **New:**
|
842 |
|
843 | ```marko
|
844 | <input type="text" key="nameInput" />
|
845 | ```
|
846 |
|
847 | Similarly, `w-for` has been been replaced with `for-key`:
|
848 |
|
849 | **Old:**
|
850 |
|
851 | ```marko
|
852 | <label w-for="nameInput">Name</label>
|
853 | <input type="text" w-id="nameInput" />
|
854 | ```
|
855 |
|
856 | **New:**
|
857 |
|
858 | ```marko
|
859 | <label for-key="nameInput">Name</label>
|
860 | <input type="text" key="nameInput" />
|
861 | ```
|
862 |
|
863 | <a name="deprecate-w-on"></a>
|
864 |
|
865 | ### Deprecate `w-on*` in favor of `on*()` ([#420](https://github.com/marko-js/marko/issues/420))
|
866 |
|
867 | **Old:**
|
868 |
|
869 | ```marko
|
870 | <button w-on-click="handleClick">click me</button>
|
871 | ```
|
872 |
|
873 | or
|
874 |
|
875 | ```marko
|
876 | <button w-onClick="handleClick">click me</button>
|
877 | ```
|
878 |
|
879 | **New:**
|
880 |
|
881 | ```marko
|
882 | <button on-click('handleClick')>click me</button>
|
883 | ```
|
884 |
|
885 | or
|
886 |
|
887 | ```marko
|
888 | <button onClick('handleClick')>click me</button>
|
889 | ```
|
890 |
|
891 | ### Deprecate `<init-widgets/>` ([#409](https://github.com/marko-js/marko/issues/409))
|
892 |
|
893 | **Old:**
|
894 |
|
895 | ```marko
|
896 | <html>
|
897 | ...
|
898 | <body>
|
899 | ...
|
900 | <init-widgets/>
|
901 | </body>
|
902 | </html>
|
903 | ```
|
904 |
|
905 | **New:**
|
906 |
|
907 | **Automatic component initialization!**
|
908 |
|
909 | ### Deprecate `w-body` and replace with `include` ([#418](https://github.com/marko-js/marko/issues/418))
|
910 |
|
911 | **Old:**
|
912 |
|
913 | ```marko
|
914 | <div>
|
915 | <h1>My Awesome Component</h1>
|
916 | <div class="body" w-body/>
|
917 | </div>
|
918 | ```
|
919 |
|
920 | **New:**
|
921 |
|
922 | ```marko
|
923 | <div>
|
924 | <h1>My Awesome Component</h1>
|
925 | <div class="body" include()/>
|
926 | </div>
|
927 | ```
|
928 |
|
929 | Or, as a tag:
|
930 |
|
931 | ```marko
|
932 | <div>
|
933 | <h1>My Awesome Component</h1>
|
934 | <div class="body">
|
935 | <include()/>
|
936 | </div>
|
937 | </div>
|
938 | ```
|
939 |
|
940 | NOTE: The parens (i.e., `()`) are optional for both the include attribute and the include tag
|
941 |
|
942 | Or, with an argument value:
|
943 |
|
944 | ```marko
|
945 | <div>
|
946 | <h1>My Awesome Component</h1>
|
947 | <div class="body">
|
948 | <include(data.renderBody || data.label)/>
|
949 | </div>
|
950 | </div>
|
951 | ```
|
952 |
|
953 | ### Deprecate `w-preserve*` and replace with `no-update*` ([#419](https://github.com/marko-js/marko/issues/419))
|
954 |
|
955 | **Old:**
|
956 |
|
957 | ```marko
|
958 | <div w-preserve>
|
959 | ...
|
960 | </div>
|
961 | ```
|
962 |
|
963 | **New:**
|
964 |
|
965 | ```marko
|
966 | <div no-update>
|
967 | ...
|
968 | </div>
|
969 | ```
|
970 |
|
971 | ### Deprecate `w-preserve-attrs` and replace with `:no-update` ([#422](https://github.com/marko-js/marko/issues/422))
|
972 |
|
973 | **Old:**
|
974 |
|
975 | ```marko
|
976 | <div style="color:#09c" w-preserve-attrs="style">
|
977 | ...
|
978 | </div>
|
979 | ```
|
980 |
|
981 | **New:**
|
982 |
|
983 | ```marko
|
984 | <div style:no-update="color:#09c">
|
985 | ...
|
986 | </div>
|
987 | ```
|
988 |
|
989 | ### Deprecate `w-extend` and allow multiple components to be bound to the same HTML element ([#392](https://github.com/marko-js/marko/issues/392))
|
990 |
|
991 | > `w-extend` is now deprecated
|
992 |
|
993 | **Old:**
|
994 |
|
995 | ```marko
|
996 | <div w-bind>
|
997 | <some-component w-onEvent="handleEvent"/>
|
998 | </div>
|
999 | ```
|
1000 |
|
1001 | or
|
1002 |
|
1003 | ```marko
|
1004 | <some-component w-extend w-onEvent="handleEvent"/>
|
1005 | ```
|
1006 |
|
1007 | **New:**
|
1008 |
|
1009 | ```marko
|
1010 | <some-component onEvent('handleEvent')/>
|
1011 | ```
|
1012 |
|
1013 | NOTE: The outer most component is what is returned when calling `getComponent()`/`getComponentForEl()`.
|
1014 |
|
1015 | <a name="breaking-changes"></a>
|
1016 |
|
1017 | ## Breaking Changes
|
1018 |
|
1019 | In order to move forward it was necessary to introduce a few (minor) breaking changes. We are also removing support for some features that were already logging deprecation messages in v3.
|
1020 |
|
1021 | ### Consistent rendering API ([#389](https://github.com/marko-js/marko/issues/389))
|
1022 |
|
1023 | **Old:**
|
1024 |
|
1025 | ```js
|
1026 | var template = require("./template.marko");
|
1027 | var component = require("./my-component");
|
1028 | var data = {};
|
1029 |
|
1030 | template.render(data); // returns `out`
|
1031 | template.render(data, (err, html, out) => {});
|
1032 | template.renderSync(data); // returns a String representing the HTML output
|
1033 |
|
1034 | component.render(data); // returns a `RenderResult`
|
1035 | component.render(data, (err, renderResult) => {});
|
1036 | component.renderSync(data); // throws an error, not a method.
|
1037 | ```
|
1038 |
|
1039 | **New:**
|
1040 |
|
1041 | ```js
|
1042 | var template = require("./template.marko");
|
1043 | var component = require("./my-component");
|
1044 | var data = {};
|
1045 |
|
1046 | template.render(data); // returns `out`
|
1047 | template.render(data, (err, out) => {});
|
1048 | template.renderSync(data); // returns `out`
|
1049 |
|
1050 | component.render(data); // returns `out`
|
1051 | component.render(data, (err, out) => {});
|
1052 | component.renderSync(data); // returns `out`
|
1053 | ```
|
1054 |
|
1055 | Also, `out` has been updated to implement DOM manipulation methods like `appendTo` that were previously only available from the `RenderResult` returned from component renders.
|
1056 |
|
1057 | NOTE: We will implement `out.toString()` and `out.toJSON()` so in many cases the `out` can be used as a string.
|
1058 |
|
1059 | ### Remove support for deprecated `empty`/`notEmpty` helpers ([#357](https://github.com/marko-js/marko/issues/357))
|
1060 |
|
1061 | > Already deprecated in v3
|
1062 |
|
1063 | The `empty`/`notEmpty` helpers were automatically being added to every compiled
|
1064 | template. While they can be helpful, we feel it is better if the developer
|
1065 | explicitly imports only the exact helpers that your code depends on for
|
1066 | improved modularity.
|
1067 |
|
1068 | ### Remove hyphenated properties from input model ([#356](https://github.com/marko-js/marko/issues/356))
|
1069 |
|
1070 | > Already deprecated in v3
|
1071 |
|
1072 | Given a template like this:
|
1073 |
|
1074 | ```marko
|
1075 | <include("./include-target.marko") first-name='Frank'/>
|
1076 | ```
|
1077 |
|
1078 | `include-target.marko` looks like:
|
1079 |
|
1080 | **Old:**
|
1081 |
|
1082 | ```marko
|
1083 | -- Hello ${data['first-name']}
|
1084 | ```
|
1085 |
|
1086 | **New:**
|
1087 |
|
1088 | ```marko
|
1089 | -- Hello ${input.firstName}
|
1090 | ```
|
1091 |
|
1092 | ### Remove support for deprecated `<async-fragment>` and related tags ([#312](https://github.com/marko-js/marko/pull/312))
|
1093 |
|
1094 | > Already deprecated in v3
|
1095 |
|
1096 | **Old:**
|
1097 |
|
1098 | ```marko
|
1099 | <async-fragment var="foo" data-provider=data.provider>
|
1100 | ${foo}
|
1101 | </async-fragment>
|
1102 | ```
|
1103 |
|
1104 | **New:**
|
1105 |
|
1106 | ```marko
|
1107 | <await(data.provider)>
|
1108 | <@then|foo|>
|
1109 | ${foo}
|
1110 | </@then>
|
1111 | </await>
|
1112 | ```
|
1113 |
|
1114 | ### Remove support for emitting deprecated `async-fragment` events ([#426](https://github.com/marko-js/marko/issues/426))
|
1115 |
|
1116 | > Already deprecated in v3
|
1117 |
|
1118 | | Old | New |
|
1119 | | ---------------------------- | --------------------- |
|
1120 | | `asyncFragmentFinish` | `await:finish` |
|
1121 | | `asyncFragmentBegin` | `await:begin` |
|
1122 | | `asyncFragmentBeforeRender` | `await:beforeRender` |
|
1123 | | `asyncFragmentClientReorder` | `await:clientReorder` |
|