1 | # Upgrading to Marko 4
|
2 |
|
3 | The following guide will help you get through the upgrade process quickly and smoothly. After any given step you should have a working application.
|
4 |
|
5 | This means you should complete a step and get it merged back into master fairly quickly. You shouldn't need to have a `marko-4-upgrade` branch for your project that lives in limbo for a couple of weeks falling behind the other changes that are being merged into master.
|
6 |
|
7 | If you do decide to pause and later jump in where you left off, be sure to repeat Step 0 first 😉.
|
8 |
|
9 | ## Step 0 - Ensure you're in a working state
|
10 |
|
11 | Run your application and tests to ensure your project is in a working state. There's little worse than finding an issue after you've started the upgrade process only to figure out the issue existed beforehand.
|
12 |
|
13 | ## Step 1 - Upgrade to latest 3.x
|
14 |
|
15 | Before we start, you'll want to make sure that you are already on the latest `3.x` release of `marko` and the latest `6.x` release of `marko-widgets`. Later versions of `marko@3` and `marko-widgets@6` ship with deprecation warnings should be handled (the next step) before upgrading to Marko 4. This will make your life _so_ much easier.
|
16 |
|
17 | ```
|
18 | npm install marko@^3 marko-widgets@^6
|
19 | ```
|
20 |
|
21 | or
|
22 |
|
23 | ```
|
24 | yarn upgrade marko@^3 marko-widgets@^6
|
25 | ```
|
26 |
|
27 | > Note: Do NOT run `npm install marko` (without the `@^3`). This will put you on Marko 4 and we're not quite there yet.
|
28 |
|
29 | ## Step 2 - Deal with deprecations
|
30 |
|
31 | Run your application and tests and ensure that there are no deprecation warnings logged to the console. If there are, you should follow the instructions in the deprecation messages to avoid the deprecated pattern and migrate to the recommended pattern. In particular:
|
32 |
|
33 | - Using `w-extend` is not supported by the compatibility layer in Marko 4, compose components instead (you'll need an extra wrapper node in Marko 3, but it can be removed once you upgrade).
|
34 |
|
35 | ```marko
|
36 | // UNSUPPORTED
|
37 | <fancy-button w-extend/>
|
38 |
|
39 | // SUPPORTED
|
40 | <span w-bind>
|
41 | <fancy-button/>
|
42 | </span>
|
43 | ```
|
44 |
|
45 | - Using `<widget-types/>` to conditionally bind to two or more different widgets is not supported by the compatibility layer in Marko 4 (using `<widget-types>` to disable binding is still supported).
|
46 |
|
47 | ```marko
|
48 | // UNSUPPORTED
|
49 | <widget-types default="./widget" mobile="./widget-mobile"/>
|
50 | <div w-bind=(data.isMobile ? 'default' : 'mobile')>
|
51 | ...
|
52 | </div>
|
53 |
|
54 | // SUPPORTED
|
55 | <widget-types default="./"/>
|
56 | <div w-bind=(data.includeWidget ? 'default' : null)>
|
57 | ...
|
58 | </div>
|
59 | ```
|
60 |
|
61 | - Using `data-widget` and `data-w-*` attributes in your application code and tests. These attributes existed so Marko could keep track of DOM nodes and they don't exist in Marko 4.
|
62 | - If you're accessing the DOM to get an Element, prefer `next/prevElementSibling`, `first/lastElementChild` and `children` instead of ~`next/prevSibling`~, ~`first/lastChild`~ and ~`childNodes`~. There are differences in the DOM structure generated by Marko 3 vs 4 and you might not get the same node after upgrading with the non-element version of these properties.
|
63 | - In Marko 3, the rendering API was different between templates (which returned strings) and widgets (which returned a RenderResult). In Marko 4, all render methods return RenderResults, so if you need a string, use `renderToString` which will still return a string after upgrading.
|
64 | - `template.render(data)` without a callback → `template.renderToString(data)`
|
65 | - `template.render(data, callback)` with a callback → `template.renderToString(data, callback)`
|
66 | - `template.renderSync(data)` → `template.renderToString(data)`
|
67 | - `widget.render(data)` without a callback → `widget.renderSync`
|
68 |
|
69 | Please note that you will need to deal with deprecations in any dependent modules as well before continuing with the upgrade process (usually this means updating dependencies).
|
70 |
|
71 | ## Step 3 - Upgrade dependencies
|
72 |
|
73 | Before upgrading to Marko 4, it is recommended to make sure that your Marko-related dependencies are up-to-date. Many packages have versions that support both Marko 3 and Marko 4. If one of your dependencies doesn't have a version that supports both, you'll need to wait to upgrade it until you're upgrading Marko.
|
74 |
|
75 | After upgrading, run your application and tests to ensure that everything is still working as intended. If there are any issues, please refer to the changelogs of the modules you just upgraded to see if you need to make any changes within your app to accommodate the new versions.
|
76 |
|
77 | ### Step 3.1 - Dependencies with widgets
|
78 |
|
79 | If you have any Marko components installed from npm, chances are at least one of them has a direct dependency on `marko@^3` or `marko-widgets@^6`. **This is bad.**
|
80 |
|
81 | Marko 4 for has legacy support for Marko 3 widgets, but if a dependency directly depends on an old version of `marko` or `marko-widgets`, it will try to use that old version and after your app is on Marko 4 this will cause all sorts of errors.
|
82 |
|
83 | You can run `npm ls marko marko-widgets` (or `yarn list marko marko-widgets`) to view any dependencies that have a direct dependency on either of these. Any packages that provide components will need to move these into `peerDependencies`.
|
84 |
|
85 | > NOTE: Some modules that have direct dependencies on Marko do not need to be updated, but as a general rule, they do.
|
86 |
|
87 | Let's take a look at what a `package.json` for a dependency _should_ look like (minus the comments, because that's not valid JSON 😉).
|
88 |
|
89 | ```js
|
90 | {
|
91 | // marko and marko-widgets are NOT here
|
92 | "dependencies":{}
|
93 |
|
94 | // use marko@3 and marko-widgets@6 for testing
|
95 | "devDependencies":{
|
96 | "marko": "^3",
|
97 | "marko-widgets": "^6"
|
98 | },
|
99 |
|
100 | // use the app's version of marko/marko-widgets, but
|
101 | // give a warning if it doesn't match the versions this
|
102 | // package is compatible with (both Marko 3 and 4)
|
103 | "peerDependencies": {
|
104 | "marko": "^3 || ^4",
|
105 | "marko-widgets": "^6 || ^7"
|
106 | }
|
107 | }
|
108 | ```
|
109 |
|
110 | #### `yarn` resolutions
|
111 |
|
112 | If you don't maintain the package that needs to move `marko` and/or `marko-widgets` to `peerDependencies`, you can make use of [yarn's selective dependency resolutions](https://yarnpkg.com/lang/en/docs/selective-version-resolutions/). Add the following to your `package.json` and it will force your dependencies to use the latest version of Marko:
|
113 |
|
114 | ```js{5-8}
|
115 | {
|
116 | "name": "appname",
|
117 | "version": "0.0.0",
|
118 | /* ... */
|
119 | "resolutions": {
|
120 | "**/marko": "^4",
|
121 | "**/marko-widgets": "^7"
|
122 | }
|
123 | }
|
124 | ```
|
125 |
|
126 | ## Step 4 - Upgrade marko
|
127 |
|
128 | Phew! With all the prep out of the way we're finally ready to upgrade `marko`!
|
129 |
|
130 | ```
|
131 | npm install marko@^4 marko-widgets@^7
|
132 | ```
|
133 |
|
134 | or
|
135 |
|
136 | ```
|
137 | yarn upgrade marko@^4 marko-widgets@^7
|
138 | ```
|
139 |
|
140 | If at this point you're thinking, "Wait... I thought Marko 4 didn't need `marko-widgets` any more...", you'd be correct. `marko-widgets@7` is just there to help with the migration. We'll remove it soon, but for now, there's still a bunch of calls to `require('marko-widgets').defineComponent` all over your app's code and we don't want that to throw saying it can't find the module.
|
141 |
|
142 | Now run your application and its tests. Marko 4 contains a legacy compatibility layer, so everything should still work! Congratulations, you've upgraded to Marko 4!
|
143 |
|
144 | You will however have noticed a swarm of deprecations in your console. We'll get to those.
|
145 |
|
146 | > NOTE: `marko-widgets@7` isn't tagged as latest, therefore `npm install marko-widgets` and `npm install marko-widgets@latest` will NOT get you to `7.x`.
|
147 |
|
148 | ## Step 5 - Deal with Marko 4 deprecations
|
149 |
|
150 | Despite having a lot of deprecation warnings, the beauty is that you can deal with them on a template by template, component by component basis and keep a working app in between migrating each template/component.
|
151 |
|
152 | Additionally, any deprecation warnings that start with `MIGRATION` are automatically migratable by [`marko migrate`](https://github.com/marko-js/cli/blob/master/packages/migrate/README.md). Most migrations are 100% safe and will run automatically. However, there are a few migrations which are considered unsafe: they may only get you 90% of the way there. These migrations will prompt and ask if you want to run the migration. It is highly recommended to run these only on a single component at a time and then finish the migration manually using the guide below so that your app is always in a working state
|
153 |
|
154 | ### 6.1 - Layouts
|
155 |
|
156 | #### `<layout-use>` → Layout components with `<@tags>` (or `import`)
|
157 |
|
158 | The layout taglib is no longer necessary in Marko 4 because components have the ability to easily recieve multiple blocks of content and can render those blocks whereever they like.
|
159 |
|
160 | You can also directly `import` a template by it's path much like `<layout-use>` and render it using the `<${dynamic}/>` syntax, but the recommended way to reference it is by creating components. You should move the layout into your `components/` directory and use it as any other component.
|
161 |
|
162 | **Old:**
|
163 |
|
164 | ```
|
165 | src/
|
166 | components/
|
167 | layouts/
|
168 | site-layout.marko
|
169 | pages/
|
170 | home/
|
171 | template.marko
|
172 | ```
|
173 |
|
174 | ```marko
|
175 | <layout-use('../../layouts/site-layout.marko')>
|
176 | <layout-put into="body">
|
177 | Hello World
|
178 | </layout-put>
|
179 | </layout-use>
|
180 | ```
|
181 |
|
182 | **New (_automatically migratable_):**
|
183 |
|
184 | ```marko
|
185 | import SiteLayout from '../../layouts/site-layout.marko';
|
186 |
|
187 | <${SiteLayout}>
|
188 | <@body>
|
189 | Hello World
|
190 | </@body>
|
191 | </>
|
192 | ```
|
193 |
|
194 | > NOTE: If you're using a layout from an npm package that requires you to reference it by its path, you can `import` it. However we recommend checking to see if there is a newer version of the package that exposes the layout as a component or updating the package to expose the layout as a component.
|
195 |
|
196 | **New (Recommended):**
|
197 |
|
198 | ```
|
199 | src/
|
200 | components/
|
201 | site-layout.marko
|
202 | pages/
|
203 | home/
|
204 | template.marko
|
205 | ```
|
206 |
|
207 | ```marko
|
208 | <site-layout>
|
209 | <@body>
|
210 | Hello World
|
211 | </@body>
|
212 | </site-layout>
|
213 | ```
|
214 |
|
215 | Related Docs: [Custom Tags](http://markojs.com/docs/custom-tags/)
|
216 |
|
217 | #### `<layout-placeholder>` → `<${dynamic}>`
|
218 |
|
219 | **Old:**
|
220 |
|
221 | ```marko
|
222 | <!doctype>
|
223 | <html>
|
224 | <body>
|
225 | <layout-placeholder name="body">
|
226 | Default body content
|
227 | </layout-placeholder>
|
228 | </body>
|
229 | </html>
|
230 | ```
|
231 |
|
232 | **New (_automatically migratable_):**
|
233 |
|
234 | ```marko
|
235 | <!doctype>
|
236 | <html>
|
237 | <body>
|
238 | <if(input.body)>
|
239 | <${input.body}/>
|
240 | </if>
|
241 | <else>
|
242 | Default body content
|
243 | </else>
|
244 | </body>
|
245 | </html>
|
246 | ```
|
247 |
|
248 | ##### Without fallback content
|
249 |
|
250 | **Old:**
|
251 |
|
252 | ```marko
|
253 | <!doctype>
|
254 | <html>
|
255 | <body>
|
256 | <layout-placeholder name="body"/>
|
257 | </body>
|
258 | </html>
|
259 | ```
|
260 |
|
261 | **New (_automatically migratable_):**
|
262 |
|
263 | ```marko
|
264 | <!doctype>
|
265 | <html>
|
266 | <body>
|
267 | <${input.body}/>
|
268 | </body>
|
269 | </html>
|
270 | ```
|
271 |
|
272 | Related Docs: [Body content](http://markojs.com/docs/body-content/)
|
273 |
|
274 | ### 6.2 - Variables & Scripts
|
275 |
|
276 | #### `<script marko-init>` → `import`/`static`
|
277 |
|
278 | The `<script marko-init>` attribute is deprecated, but in its place you get ES Module `import` syntax and the `static` keyword. Anything after the `static` keyword is executed as JavaScript _when the template is loaded_.
|
279 |
|
280 | **Old:**
|
281 |
|
282 | ```marko
|
283 | <script marko-init>
|
284 | var capitalize = require('./util/caps');
|
285 | var NAME = capitalize('Frank');
|
286 | </script>
|
287 |
|
288 | <div>${NAME}</div>
|
289 | ```
|
290 |
|
291 | **New (_automatically migratable_):**
|
292 |
|
293 | ```marko
|
294 | import capitalize from './util/caps';
|
295 | static var NAME = capitalize('Frank');
|
296 |
|
297 | <div>${NAME}</div>
|
298 | ```
|
299 |
|
300 | Related Docs:
|
301 |
|
302 | - [Static JavaScript](http://markojs.com/docs/syntax/#static-javascript)
|
303 | - [Importing external files](http://markojs.com/docs/syntax/#importing-external-files)
|
304 |
|
305 | #### `<var>`/`<assign>`/`<invoke>` → `$`
|
306 |
|
307 | The `<var>` tag is deprecated, but in its place you get `$`. Similar to `static`, a line that begins with `$` will execute the JavaScript that follows _as a part of each render_.
|
308 |
|
309 | **Old:**
|
310 |
|
311 | ```marko
|
312 | <var name="Frank"/>
|
313 | <assign name="John"/>
|
314 | <invoke console.log(name)/>
|
315 | <div>${name}</div>
|
316 | ```
|
317 |
|
318 | **New (_automatically migratable_):**
|
319 |
|
320 | ```marko
|
321 | $ var name = "Frank";
|
322 | $ name = "John";
|
323 | $ console.log(name);
|
324 | <div>${name}</div>
|
325 | ```
|
326 |
|
327 | Related Docs: [Inline JavaScript](http://markojs.com/docs/syntax/#inline-javascript)
|
328 |
|
329 | ### 6.3 - `w-*` Atrributes
|
330 |
|
331 | #### `w-id` → `key`
|
332 |
|
333 | **Old:**
|
334 |
|
335 | ```marko
|
336 | <div w-id="foo"/>
|
337 | ```
|
338 |
|
339 | **New:**
|
340 |
|
341 | ```marko
|
342 | <div key="foo"/>
|
343 | ```
|
344 |
|
345 | Related Docs: [The `key` attribute](http://markojs.com/docs/class-components/#key)
|
346 |
|
347 | #### `w-for` → `for:scoped`
|
348 |
|
349 | **Old:**
|
350 |
|
351 | ```marko
|
352 | <label w-for="name">Name</label>
|
353 | <input type="text" w-id="name"/>
|
354 | ```
|
355 |
|
356 | **New (_automatically migratable_):**
|
357 |
|
358 | ```marko
|
359 | <label for:scoped="name">Name</label>
|
360 | <input type="text" id:scoped="name"/>
|
361 | ```
|
362 |
|
363 | Related Docs: [The `:scoped` attribute modifier](http://markojs.com/docs/class-components/#scoped)
|
364 |
|
365 | #### `widget.elId` → `:scoped`
|
366 |
|
367 | You can use `:scoped` on any attribute to reference a scoped value. This value will be unique to this component instance and is useful for other attributes that take an `id` to reference, so you can use a scoped `id` instead.
|
368 |
|
369 | **Old:**
|
370 |
|
371 | ```marko
|
372 | <button aria-describedby=widget.elId("tooltip")>...</button>
|
373 | <div w-id="tooltip" role="tooltip">...</div>
|
374 | ```
|
375 |
|
376 | **New (_automatically migratable_):**
|
377 |
|
378 | ```marko
|
379 | <button aria-describedby:scoped="tooltip">...</button>
|
380 | <div id:scoped="tooltip" role="tooltip">...</div>
|
381 | ```
|
382 |
|
383 | Related Docs: [The `:scoped` attribute modifier](http://markojs.com/docs/class-components/#scoped)
|
384 |
|
385 | #### `w-preserve` → `no-update`
|
386 |
|
387 | **Old:**
|
388 |
|
389 | ```marko
|
390 | <div w-preserve>
|
391 | ...
|
392 | </div>
|
393 | ```
|
394 |
|
395 | **New (_automatically migratable_):**
|
396 |
|
397 | ```marko
|
398 | <div no-update>
|
399 | ...
|
400 | </div>
|
401 | ```
|
402 |
|
403 | #### `w-preserve-attrs` → `:no-update`
|
404 |
|
405 | **Old:**
|
406 |
|
407 | ```marko
|
408 | <div class="foo" w-preserve-attrs="class">
|
409 | ...
|
410 | </div>
|
411 | ```
|
412 |
|
413 | **New (_automatically migratable_):**
|
414 |
|
415 | ```marko
|
416 | <div class:no-update="foo">
|
417 | ...
|
418 | </div>
|
419 | ```
|
420 |
|
421 | #### `w-on-*` → `on-*()`
|
422 |
|
423 | **Old:**
|
424 |
|
425 | ```marko
|
426 | <button w-on-click="handleClick">click me</button>
|
427 | ```
|
428 |
|
429 | or
|
430 |
|
431 | ```marko
|
432 | <button w-onClick="handleClick">click me</button>
|
433 | ```
|
434 |
|
435 | **New (_automatically migratable_):**
|
436 |
|
437 | ```marko
|
438 | <button on-click('handleClick')>click me</button>
|
439 | ```
|
440 |
|
441 | The new syntax support binding additional arguments.
|
442 |
|
443 | Related Docs: [Listening to events](http://markojs.com/docs/events/#listening-to-events)
|
444 |
|
445 | ### 6.4 Widgets → Components
|
446 |
|
447 | It's time to migrate your first legacy (Marko 3 style) widget to a Marko 4 component. Before you continue, please note that you'll need to go through all these steps for a given component. Partially migrated components will break your app. This is the section for which there are unsafe migrations provided by `marko migrate`. Again, these migrations should be run on a single component, and then follow the steps below to ensure the component is fully migrated.
|
448 |
|
449 | #### Remove `w-bind`
|
450 |
|
451 | `w-bind` is the indicator used by Marko to determine whether a component should operate in legacy mode. Marko 4 automatically binds the top level elements in a component, so `w-bind` is not necessary. Let's remove it. There's no turning back now...
|
452 |
|
453 | #### Rename widget methods
|
454 |
|
455 | - `this.getWidget` → `this.getComponent`
|
456 | - `this.getWidgets` → `this.getComponents`
|
457 |
|
458 | Related Docs:
|
459 |
|
460 | - [`getComponent()`](http://markojs.com/docs/class-components/#getcomponentkey-index)
|
461 | - [`getComponents()`](http://markojs.com/docs/class-components/#getcomponentskey-index)
|
462 |
|
463 | #### Component filename structure
|
464 |
|
465 | ##### Traditional widget
|
466 |
|
467 | **Old:**
|
468 |
|
469 | Marko 3 widgets were traditionally structured as follows:
|
470 |
|
471 | ```
|
472 | components/
|
473 | my-cool-component/
|
474 | index.js → Widget Definition
|
475 | template.marko → Widget Marko Template
|
476 | ```
|
477 |
|
478 | Your `index.js` acts as the entry point for the component, contains a call to `require('marko-widgets').defineComponent` and requires `template.marko`.
|
479 |
|
480 | **New:**
|
481 |
|
482 | Marko 4 changes the filename structure and makes the template the entry point for the component:
|
483 |
|
484 | - `template.marko` → `index.marko` (the template)
|
485 | - `index.js` → `component.js` (component behavior for server/client)
|
486 |
|
487 | Thus a full split file based component in Marko 4 would be structured as follows:
|
488 |
|
489 | ```
|
490 | components/
|
491 | my-cool-component/
|
492 | component.js → Component Definition
|
493 | index.marko → Component Marko Template
|
494 | ```
|
495 |
|
496 | Marko 4 also introduces [single file components](http://markojs.com/docs/class-components/#single-file-components) within `index.marko`.
|
497 |
|
498 | ##### Split renderer/widget
|
499 |
|
500 | **Old:**
|
501 |
|
502 | Marko 3 Split renderer/widgets were structured as follows:
|
503 |
|
504 | ```
|
505 | components/
|
506 | my-cool-component/
|
507 | renderer.js → Renderer Definition
|
508 | template.marko → Widget Marko Template
|
509 | widget.js → Widget Definition
|
510 | ```
|
511 |
|
512 | Your `renderer.js` acts as the entry point for the component, contains a call to `require('marko-widgets').defineRenderer` and requires `template.marko`.
|
513 |
|
514 | Your `widget.js` should contain a call to `require('marko-widgets').defineWidget`.
|
515 |
|
516 | **New:**
|
517 |
|
518 | Marko 4 changes the filename structure and makes the template the entry point for the component:
|
519 |
|
520 | - `template.marko` → `index.marko` (the template)
|
521 | - `renderer.js` → `component.js` (component behavior for server/client)
|
522 | - `widget.js` → `component-browser.js` (component behavior for client only & causes `component.js` to be server only).
|
523 |
|
524 | Thus a full component in Marko 4 would be structured as follows:
|
525 |
|
526 | ```
|
527 | components/
|
528 | my-cool-component/
|
529 | component.js → Component Definition (Server)
|
530 | component-browser.js → Component Definition (Browser)
|
531 | index.marko → Component Marko Template
|
532 | ```
|
533 |
|
534 | #### Remove all references to `marko-widgets`
|
535 |
|
536 | **Old:**
|
537 |
|
538 | As noted in file structure section above, Marko 3 used `marko-widgets` to define a component within each `index.js`. This was the entry point for the component and it required `template.marko` so it knew how to render itself.
|
539 |
|
540 | _index.js_
|
541 |
|
542 | ```js
|
543 | module.exports = require("marko-widgets").defineComponent({
|
544 | template: require("./template.marko")
|
545 | // ...
|
546 | });
|
547 | ```
|
548 |
|
549 | **New:**
|
550 |
|
551 | In Marko 4, `marko-widgets` is no longer necessary and `index.marko` becomes the component entry point so referencing the template from the `component.js` file is not necessary (and might cause circular dependency issues).
|
552 |
|
553 | _component.js_
|
554 |
|
555 | ```js
|
556 | module.exports = {
|
557 | // ...
|
558 | };
|
559 | ```
|
560 |
|
561 | > NOTE: Once this step has been completed for all components in a project, you can remove `marko-widgets` as a dependency!
|
562 |
|
563 | #### `data` → `input`/`state`
|
564 |
|
565 | **Old:**
|
566 |
|
567 | In Marko 3, a template received a single `data` variable that contained the input data. In the case of a widget, the `getTemplateData` method could be used to combine the widget state with the widget input data into a single `data` object to be passed to the template.
|
568 |
|
569 | _index.js_
|
570 |
|
571 | ```js
|
572 | // ...
|
573 | getTemplateData(state, input) {
|
574 | return {
|
575 | foo: state.foo,
|
576 | bar: input.bar
|
577 | }
|
578 | }
|
579 | // ...
|
580 | ```
|
581 |
|
582 | _template.marko_
|
583 |
|
584 | ```marko
|
585 | <ul>
|
586 | <li>Foo: ${data.foo}</li>
|
587 | <li>Bar: ${data.bar}</li>
|
588 | </ul>
|
589 | ```
|
590 |
|
591 | **New:**
|
592 |
|
593 | In Marko 4, components are passed the input data as `input` and a separate `state` variable contains component state. This removes the need for `getTemplateData`! (And it is no longer called)
|
594 |
|
595 | _index.marko_
|
596 |
|
597 | ```marko
|
598 | <ul>
|
599 | <li>Foo: ${state.foo}</li>
|
600 | <li>Bar: ${input.bar}</li>
|
601 | </ul>
|
602 | ```
|
603 |
|
604 | _component.js_
|
605 |
|
606 | ```js
|
607 | // getTemplateData is removed
|
608 | ```
|
609 |
|
610 | > NOTE: `input` is aliased as `data`, so accessing `data` will still work, but it is recommended to use `input`. Accessing `data` will be officially deprecated at a later date.
|
611 |
|
612 | ##### Data massaging
|
613 |
|
614 | If your `getTemplateData` has a lot of logic in it to transform the `state` or `input`, you'll probably want to retain that logic, but still remove the `getTemplateData` method.
|
615 |
|
616 | **Old:**
|
617 |
|
618 | _index.js_
|
619 |
|
620 | ```
|
621 | // ...
|
622 | getTemplateData: function(state, input) {
|
623 | var value = state.value;
|
624 | var sign;
|
625 |
|
626 | if (value < 0) {
|
627 | sign = 'negative';
|
628 | } else if (value > 0) {
|
629 | sign = 'positive';
|
630 | }
|
631 |
|
632 | return {
|
633 | value: value,
|
634 | sign: sign
|
635 | };
|
636 | }
|
637 | // ...
|
638 | ```
|
639 |
|
640 | _template.marko_
|
641 |
|
642 | ```marko
|
643 | <div class=data.sign>
|
644 | ${data.value}
|
645 | </div>
|
646 | ```
|
647 |
|
648 | **New:**
|
649 |
|
650 | Instead of manipulating `input`/`state` before it makes it to the template, move the manipulation logic from `getTemplateData` into a helper function that can be imported into your template. This has the added benefit that it is now easy to write unit tests for any helper functions you might have.
|
651 |
|
652 | _helpers.js_
|
653 |
|
654 | ```
|
655 | exports.getSign = function(value) {
|
656 | var sign;
|
657 |
|
658 | if (value < 0) {
|
659 | sign = 'negative';
|
660 | } else if (value > 0) {
|
661 | sign = 'positive';
|
662 | }
|
663 |
|
664 | return sign;
|
665 | }
|
666 | ```
|
667 |
|
668 | _index.marko_
|
669 |
|
670 | ```marko
|
671 | import { getSign } from './helpers';
|
672 |
|
673 | <div class=getSign(state.value)>
|
674 | ${state.value}
|
675 | </div>
|
676 | ```
|
677 |
|
678 | #### Initializing state
|
679 |
|
680 | **Old:**
|
681 |
|
682 | In Marko 3, `input` was transient: it was only there for the first render (or when _new_ input was passed into a component). This meant that when your component re-rendered, if there was data that was in the `input` that was necessary for a re-render, you had to put it in `state` to make sure it got kept around.
|
683 |
|
684 | _index.js_
|
685 |
|
686 | ```js
|
687 | // ...
|
688 | getInitialState(input) {
|
689 | return {
|
690 | count: input.initialCount || 0,
|
691 | color: input.color
|
692 | }
|
693 | }
|
694 | // ...
|
695 | ```
|
696 |
|
697 | _template.marko_
|
698 |
|
699 | ```marko
|
700 | <div style={ color:data.color }>
|
701 | ${data.count}
|
702 | </div>
|
703 | ```
|
704 |
|
705 | **New:**
|
706 |
|
707 | In Marko 4, `getInitialState` is no longer called. You can set initial state in `onCreate`. If you have some state that is derived from `input` and should be reset when the `input` changes, you can set it in `onInput`, but this should be a rare occurrence.
|
708 |
|
709 | Marko 4 keeps the original `input` around for subsequent renders, so you don't need to add `input` properties into the `state`. Only values that are controlled by the component should be put in `state`.
|
710 |
|
711 | _component.js_
|
712 |
|
713 | ```js
|
714 | // ...
|
715 | onCreate(input) {
|
716 | this.state = {
|
717 | count: input.initialCount || 0
|
718 | };
|
719 | }
|
720 | // ...
|
721 | ```
|
722 |
|
723 | _index.marko_
|
724 |
|
725 | ```marko
|
726 | <div style={ color:input.color }>
|
727 | ${state.count}
|
728 | </div>
|
729 | ```
|
730 |
|
731 | > NOTE: From within the component you can access `this.state` as well as `this.input`
|
732 |
|
733 | Related Docs:
|
734 |
|
735 | - [`onCreate`](http://markojs.com/docs/class-components/#oncreateinput-out)
|
736 | - [`onInput`](http://markojs.com/docs/class-components/#oninputinput-out)
|
737 | - [`this.state`](http://markojs.com/docs/class-components/#thisstate)
|
738 | - [`this.input`](http://markojs.com/docs/class-components/#thisinput)
|
739 |
|
740 | #### Browser initialization
|
741 |
|
742 | **Old:**
|
743 |
|
744 | In Marko 3, the `init` method was used to set things up in the browser and was the first time the DOM for the component was ready since `init` was called immediately after _mounting_ the component to the DOM.
|
745 |
|
746 | The `getWidgetConfig` method was used to create a `config` object that would be serialized and sent to the browser to be used in the `init` method. This was necessary because `input` was not available in `init`.
|
747 |
|
748 | _index.js_
|
749 |
|
750 | ```js
|
751 | // ...
|
752 | init(config) {
|
753 | $(this.el).dataTable(config);
|
754 | }
|
755 | getWidgetConfig(input) {
|
756 | return {
|
757 | paginate: input.paginate,
|
758 | scrollY: input.scrollY
|
759 | };
|
760 | }
|
761 | // ...
|
762 | ```
|
763 |
|
764 | **New:**
|
765 |
|
766 | `init` has been renamed to the more appropriate `onMount` which better describes where in a component's lifecycle it is called. `getWidgetConfig` is no longer necessary (or called) because we can access `this.input`.
|
767 |
|
768 | _component.js_
|
769 |
|
770 | ```js
|
771 | // ...
|
772 | onMount() {
|
773 | $(this.el).dataTable({
|
774 | paginate: this.input.paginate,
|
775 | scrollY: this.input.scrollY
|
776 | });
|
777 | }
|
778 | // ...
|
779 | ```
|
780 |
|
781 | > NOTE: If you need values from `out`, you can grab them in `onCreate`, attach them to the component instance and access them in `onMount`:
|
782 | >
|
783 | > ```js
|
784 | > //...
|
785 | > onCreate(input, out) {
|
786 | > this.value = out.global.value;
|
787 | > }
|
788 | > onMount() {
|
789 | > console.log(this.value)
|
790 | > }
|
791 | > // ...
|
792 | > ```
|
793 |
|
794 | Related Docs: [`onMount`](http://markojs.com/docs/class-components/#onmount)
|
795 |
|
796 | #### Widget body
|
797 |
|
798 | **Old:**
|
799 |
|
800 | ```js
|
801 | // ...
|
802 | getInitialBody(input, out) {
|
803 | return input.renderBody || input.label
|
804 | }
|
805 | //
|
806 | ```
|
807 |
|
808 | ```marko
|
809 | <button>
|
810 | <w-body/>
|
811 | </button>
|
812 | ```
|
813 |
|
814 | **New:**
|
815 |
|
816 | ```marko
|
817 | <button>
|
818 | <if(input.renderBody)>
|
819 | <${input.renderBody}/>
|
820 | </if>
|
821 | <else>
|
822 | ${input.label}
|
823 | </else>
|
824 | </button>
|
825 | ```
|
826 |
|
827 | #### `getInitialProps(input, out)`
|
828 |
|
829 | **Old:**
|
830 |
|
831 | This method existed because `input` was passed to `getInitialState`, `getInitialBody`, `getWidgetConfig` and `getTemplateData`. If the input needed to be transformed, `getInitialProps` allowed you to do it in a single place.
|
832 |
|
833 | **New:**
|
834 |
|
835 | `getInitialProps` is no longer called. If you need to transform your input, move that logic into helper methods or another appropriate location.
|
836 |
|
837 | #### Lifecycle methods
|
838 |
|
839 | A few of these have already been covered:
|
840 |
|
841 | - `init` ➔ `onMount`
|
842 | - `getWidgetConfig` ➔ `onMount`/`onCreate`
|
843 | - `getInitialState` ➔ `onCreate`
|
844 | - `getTemplateData` ➔ (no longer needed)
|
845 | - `getInitialProps` ➔ (no longer needed)
|
846 | - `getInitialBody` ➔ (no longer needed)
|
847 |
|
848 | ##### `onRender`
|
849 |
|
850 | The legacy `onRender` method was called with `firstRender === true` [immediately after mounting the widget in the DOM](https://github.com/marko-js/marko-widgets/blob/5b226b9ede8227bb68e16fa8316ef3058daf3d06/lib/init-widgets-browser.js#L205-L209).
|
851 |
|
852 | Subsequent calls `onRender` occurred [immediately after calls to `onUpdate`](https://github.com/marko-js/marko-widgets/blob/5b226b9ede8227bb68e16fa8316ef3058daf3d06/lib/init-widgets-browser.js#L195-L196).
|
853 |
|
854 | This behavior did not align with where the actual render takes place (it actually occurs before mounting and before updating the DOM). So we've changed its behavior in Marko 4. If you were using `onRender` in Marko 3, use `onMount` or `onUpdate` instead.
|
855 |
|
856 | - `onRender` (first render) ➔ `onMount`
|
857 | - `onRender` (subsequent renders) ➔ `onUpdate`
|
858 |
|
859 | ##### `onBeforeUpdate` and `onUpdate`
|
860 |
|
861 | - `onBeforeUpdate` ➔ `onUpdate`/`onRender`
|
862 | - `onUpdate` ➔ `onUpdate`
|
863 |
|
864 | The `onUpdate` is called after DOM updates have been made. The `onRender` method is now called before rendering, so it can replace some use-cases of `onBeforeUpdate`.
|
865 |
|
866 | ##### `onBeforeDestroy` and `onDestroy`
|
867 |
|
868 | - `onBeforeDestroy` ➔ `onDestroy`
|
869 | - `onDestroy` ➔ `onDestroy`
|
870 |
|
871 | The `onDestroy` is now called immediately before destroying the DOM associated with a component.
|
872 |
|
873 | See how the [legacy adaptor remaps these methods](https://github.com/marko-js/marko/blob/373a7ceb52b60454a9661fc94a3c9935e5a7dbfa/src/components/legacy/defineWidget-legacy-browser.js#L72-L102).
|
874 |
|
875 | Related Docs: [Lifecycle events](http://markojs.com/docs/class-components/#lifecycle-events)
|
876 |
|
877 | #### Fin.
|
878 |
|
879 | 👍🎉 You've fully migrated your first component! 🎉👍
|
880 |
|
881 | Repeat this process for each component in your app. As you get familiar with "Thinking in Marko 4" each one will be easier. And remember, you should have a working application after converting each individual component, so you don't have to do it all at once.
|