I’ve just finished reading the top-level set of Vue.js guides published on their documentation site. This followed a quick hands-on run through their Quick Start application and tutorial. I learned a lot about what Vue is (and just as importantly, what it is not.) As an added bonus, it hasn’t been too long since I went through their Angular framework counterparts. (Shopping cart on StackBlitz, Tour of Heroes hands on, and a few developer guides.) Running through these two frameworks back-to-back lets me compare and contrast their design decisions. See how they solve the common problems web developers encounter.
Vue.js is very flexible in how it can be deployed, making it a great option for developers who find Angular’s all-in-one package too constrictive for their preferences. Vue is also lighter weight: an empty Vue app is a tiny fraction of the size of equivalent empty Angular app, and this was possible because Vue is a newer framework with finer-grained modularization. Newer means there’s less legacy compatibility code to carry forward, and some framework level features are no longer necessary because they are included in newer browsers. More extensive modularization means some features inherent to Angular (and thus must be part of an empty app) are optional components in Vue and can be excluded.
But such flexibility also comes with downsides for a beginner, because every “you are free to do what you want” is also a “you have to figure it out on your own.” This was why I got completely lost looking at Polymer/Lit. I thought Vue occupied a good middle ground between the restrictive “do it our way” Angular design and the disorienting “do whatever you want” of Polymer/Lit. In the short term I think I will continue learning web development within Angular, because it is a well-defined system I can use. If I stick with learning web development, I expect I’ll start feeling Angular’s rigidity cramps my style. When that happens, I’ll revisit Vue.
As a beginner to Vue.js development I doubt I’d really need to worry about following best practices, but it was good to skim through that information just to see what lies down the road. Likewise, I didn’t expect to get very much out of the “Extra Topics” section of Vue.js documentation but I skimmed through it anyway to see what I can pick up. More than I thought I would, actually!
I haven’t done enough web development to understand all of the scenarios outlined in “Ways of Using Vue” but I do understand several of them. And I think I understood enough of the rest to recognize situations if they should come up in a “Ah, that’s what document was talking about” way. The overarching lesson is that: Vue is happy to work in several different shapes and sizes.
I enjoyed getting a look under the hood with “Reactivity in Depth“. I don’t understand enough about JavaScript proxies to use it in my own code, but it was good to see some of the benefits and pitfalls of working within Vue’s usage of the mechanism. It mostly boils down to the fact a proxy is one level of indirection from the real object. Unfortunately, one of the downsides appears to be that additional effort must be made to make sure TypeScript understands data types through this layer of indirection.
Coming from a background of strongly-typed languages like C, I was not a fan of the free-form chaos freedom of JavaScript. Using TypeScript imposes some order to the madness, and I appreciate that. Vue.js itself was written with TypeScript and supports TypeScript Vue.js application code, but doing so wasn’t as straightforward as I had thought it might be. This is especially true of the simpler Options API, partly because JavaScript usage was so simple that additional effort to be TypeScript-friendly seems like a huge imposition. In contrast, Composition API takes more effort to write but it also resembles “normal” code more so TypeScript was a better fit. After reading through TypeScript sections and “Composition API FAQ” I wonder if I’d be better off focusing on the more powerful and TypeScript-friendly Composition API for my own Vue.js projects. I’ll push that decision off to later.
The “Rendering Mechanism” and “Render Functions and JSX” sections were a look under a hood of a different part of Vue.js: outputting Vue.js component data into HTML markup for the browser. I’m not as familiar with this problem space, so I didn’t understand all the problems solved by these approaches. I don’t foresee myself writing custom rendering functions, and hopefully I won’t have to debug standard rendering functions.
I enjoyed seeing some of the fun things possible with CSS animations in “Animation Techniques” but that’s further off in the future. I would want to get a functional understanding of building Vue.js applications before I worry about visual polish, and I would lean on libraries before I start fiddling with details for myself.
Speaking of the future, I appreciated “Vue and Web Components” because it addressed one of my open questions: how much of frameworks like Angular or Vue will remain relevant in the future as web standards evolve? Many of web development frameworks arose to solve problems people had with browser-supported standards. These fed into evolution of web standards and eventually, browsers incorporated those lessons rendering many web tools no longer relevant. This “Vue and Web Components” section explained how it’s not a conflict, at least today. Vue has features not yet on the roadmap for standardized web components. Furthermore: peaceful coexistance is possible: Vue components can be self-contained into a standard web component for use elsewhere, and Vue applications can consume standard web components. This is encouraging, and it’ll be interesting to see this situation evolve in the future. For now, I’m going to wrap up this Vue.js learning session.
As a Vue.js beginner, I doubt I’ll be building large scope projects in the near future. Despite that fact, I skimmed through solutions for scaling up a Vue.js application just to get a glimpse of what that involves. After that section of Vue.js documentation was “Best Practices”, and some of the items they called out were interesting.
When I tried out Vue.js Quick Start application, I was impressed by how small it was relative to the bare-bones boilerplate generated by Angular command line tools. I was curious what Vue.js would say about “Production Deployment” to make it even smaller and faster, and the answer is “not much”. The boilerplate build process with Vite apparently generates quite small code and any improvements would require domain-specific (and/or scenario-specific) optimizations.
Moving the focus from download size to runtime speed, there are a few tips for performance optimizations. As a Vue beginner, there weren’t much for me to absorb from this page. Either they are broad general optimization considerations that I’ve known from other contexts, or they dive into the deep weeds (what’s a shallowReactive?) that I didn’t yet understand.
I was glad to see Accessibility get its own section under Best Practices, but was mildly disappointed that its content appeared to be a rehash of general web accessibility concepts without any Vue-specific features. It would be nice if Vue directly help make building accessible sites easier, but I’ll settle for the topic at least getting mentioned even if generically.
For Security, it was great that their first and foremost item is Never Use Untrusted Templates. Good tip! About half of the rest of this page are variations on the same theme, giving examples of how user-provided information can be abused and how developers need to design against those abuses. Vue has minor protections in place, such as automatic escaping of strings, but it’s not foolproof. The developer must stay on guard.
These are all good tips, putting me in a good mood to finish off this set of Vue.js developer guides.
Reading through Vue.js documentation on “Built-in Components” I learned about powerful tools doing things a Vue application developer could not do on their own. But as a practically matter, I don’t think a beginner like myself would need to worry about them just yet. The same goes for the next section: “Scaling Up”, but I still wanted to skim through to see what scaling problems Vue thought deserved solving. This section was shorter than I had anticipated, because a lot of complexity has been delegated to other tools. These core Vue.js documentation pages would link to their documentation.
The section actually starts small by talking about Vue components and the Single-File Component (SFC) format because that is the basic unit of building large Vue apps. One of the first items is actually a pointer to scaling down via petite-vue for the progressive enhancement (sprinkle tiny bits of Vue in static site) usage scenario. Then it talks about tooling like an extension for editing Vue code in Visual Studio Code, and browser extension for debugging Vue code in browser.
The topic then moves to testing. Generic web application testing frameworks would work, but the Vitest test runner is optimized for unit testing Vue components. Once we get beyond unit tests, there’s a pointer to Cypress and a link to a Vitest vs. Cypress comparison.
For client-side routing, documentation gives a simple router implementation and a reference to Vue Router for a full power routing solution. For state management, we again have a simple implementation and a reference to Pinia for a more complete solution.
Server-side rendering (SSR) can get very complex, so the discussion started with the very important question: do we really need it? Sometimes goals can be satisfied with static-site generation (SSG) in which case we can use VitePress. But if actual SSR is required, there are multiple paths depending on the developer’s desire for control. Higher-level tools include Nuxt.js, which can also do static-site generation. And Quasar, which proclaims PWA capability as well as compiling to mobile apps and browser extensions. Plus it features its own Material Design compliant UI components, making it appear to be the “and the kitchen sink” solution. Which, depending on the project, may or may not be a fit for following best practices.
While reading Vue.js documentation on reusability, I learned of VueUse library filled with composable code available for use in our Vue apps. With that in mind, I was curious about the next section of documentation: “Built-in Components.” What’s special about these components? They must enable features requiring internal Vue support and could not be done (at least not efficiently) by an external code module. From what I can tell, they have one thing in common: they hook into platform changes to component structure.
First example supports transition animations. CSS defines a transition animation mechanism but that only applies to changing properties on an object. Animating the objects themselves being changed requires support like Vue’s <Transition> for individual components and <TransitionGroup> for elements in a list. By default, adding or removing components from the application means an abrupt and instantaneous change that may leave the user disoriented. These components allow designer to add transition animation to visually guide the user through such changes for a better user experience. Both by adding/removing classes to trigger CSS transition animation plus JavaScript hooks for whatever can’t be done via CSS.
An example of a feature that requires framework level support is Transition Mode, which manages how the old and new components interact to mitigate visually jarring artifacts. Moving from “mitigating a bad thing” to “enabling a great thing” is the Move Transitions demo for <TransitionGroup>, a great way to visually inform a user of changes in a list. After seeing that, I’ve become a fan of <Transition> / <TransitionGroup>. Sure, they can be abused just like all mechanisms designed to attract user attention (Die <blink> Die) but there’s plenty of room for subtle and tasteful designs. On the downside, I’m not a fan of Named Transitions which introduces more name magic to Vue.
In the spirit of Vue keeping things lightweight and not reinventing wheels, <Transition> / <TransitionGroup> only enables transitions, they do not define any animations themselves. This page links to animation libraries (Animate.css, GreenSock, Anime.js, Motion One) that it plays well with.
As much as I love some of the animations demonstrated here, as a practical matter I’m not likely to use any of these directly in the near future. If I start building projects with Vue, I’ll start without worrying about visual polish. For a first pass on visual polish, I’ll probably use something like Vue Material. Crafting my own visual styles with transition animations will be much further down the line, if ever.
I’m equally unlikely to use anything else in Vue’s “Built-in Components” section. <KeepAlive> keeps a <component> node alive to keep its state, in the expectation of eventual reinsertion back into the tree. <Teleport> moves visual elements somewhere outside of their proper location in Vue component hierarchy, useful for global model display dialog boxes. And <Sense> is still an experimental feature for consolidating visual feedback of multiple child asynchronous operations. (One spinning “waiting” animation instead of each component having their own.) <Sense>combined with<Transition> and <KeepAlive> to handle Vue Router changes is far too advanced of a technique for this beginner to worry about.
Which is true of most of the rest of Vue.js guides, but I wanted to skim over them anyway starting with “Scaling Up”.
Vue’js documentation’s “Components In-Depth” section gave me a pretty good idea of how Vue components are implemented and interact with each other. It’s a powerful mechanism of code organization and reuse, and I found it strange Codecademy’s “Learn Vue.js” course didn’t go into any details on componentization at all. Still, as useful as Vue components are, they can’t do everything and there are a few other mechanisms for code reusability in Vue.js. After reading “Reusability” section, my takeaway is that a beginner should know how to use these mechanisms: both to recognize their presence in example code and benefiting from work shared by others. In contrast, implementing these mechanisms is a more advanced topic a beginner can postpone until later.
The most significant page in this section is the first one: Vue Composables. These are self-contained packages of pure logic without a visual representation. In that regard it has some resemblance to Angular services but more limited in scope (which probably also means it is lighter weight.) It is possible to implement composable capability as a standard Vue component with no visual template (renderless component) but that incurs wasted overhead. For an even better idea of how a composable relate to other code reuse mechanisms, there’s a comparisons section on this page.
I was most fascinated by the async state composable example, because it seems to be a way to solve many of the problems RxJS wants to solve but with less of a learning curve. Also, this is optional versus RxJS which is required to make real use of Angular. But if we really want RxJS, there exist Vue composable to interoperate with RxJS. It is part of VueUse, a collection of Vue composition utilities that cover a lot of ground. I see stuff to help with concepts like an app going full screen, an app that wants to keep the device awake, and to help an app communicate over web sockets. Some of these aren’t terribly complex to implement on our own (like full screen) but using one of these composable component might be even easier.
Following the long and instructive page on Vue composable, there were two more pages far shorter in length. First is custom directives, a mechanism for installing and using code that needs direct low-level HTML DOM manipulation. It reads like a niche tool useful as a tool of last resort for things that can’t be done any other way. The page ends with “In general, it is not recommended to use custom directives on components.” And second page covers plugins, a mechanism to install functionality at the app level. We are warned to use plugins sparingly as too many of them start running into name conflicts and other general downsides of global code. This is partly because a plugin interacts with the rest of the app via non-plugin-specific mechanisms like Provide/Inject, custom directives, and attaching to global properties (app.config.globalProperties). The example on page shows two ways to do internationalization string plugin: attached to global properties, and provide/inject.
That’s a lot of different ways we can package Vue code to be reusable, but they’re limited in how they can participate in framework-level activities. For those, we need to use Vue’s built-in components.
Vue.js documentation “Essentials” section ended with a page on “Component Basics”. That lead to the next section “Components In-Depth” covering more details on how and when to use Vue.js components and how they interact with other components in the app. After reading the section, a few items caught my attention that may or may not be significant once I start getting hands-on with Vue.
Naming
Names for Vue.js components are PascalCase by convention but that has problems with using them in HTML attributes that are liable to coerce everything to lowercase. The same problem applies to Vue.js events which are camelCase by convention. Vue.js includes magic to look at equivalent kebab-case names. I guess it solves the problem but seems like a source for future problems. What if multiple cased versions exist in code, which one runs first?
Name collisions could also happen due to a feature called “fallthrough attributes”. A component with its own “click” could have another “click” fall through from above. Which one has priority? A declared emitted “click” handler would override native “click”. A fallthrough “click” and an emitted “click” would… both be called? Wild.
I like that Vue.js works to magically fix up these kinds of problems, but I’m uncomfortable magic renaming mechanisms exist. Historically they were a high risk for bugs.
Props and Events
Props are intended to be one-way, from parent to child. But because it’s valid to pass objects, a child can modify those object instance and potentially change parent state. For performance reasons, Vue doesn’t try to detect or prevent such “deep change”. Documentation boils down to “Don’t do that” though it did describe a few potential scenarios and their workarounds. The right way for a child to communicate with parent is with emitted events, which are raised only on its direct parent.
Props go from parent to immediate child, emitted events are raised from child to immediate parent. Going beyond that (communicating across multiple layers in the hierarchy) could get messy. I saw mention for one mechanism called State Management, which will be covered in the “Scaling Up” section. There’s also the Provide/Inject mechanism, which isn’t exactly the same as Angular services but has a few similarities. To make them properly reactive values require computed() which is normally just for Vue.js Composition API. To read more about computed(), we are instructed to go back and read certain documentation sections while set to Composition API and not Options API.
Props can have default values when not assigned by parent and/or marked as “required”. To help keep objects well-behaved, we can validate values for props to ensure information coming in to a component is within expected range. We can also validate events, which isn’t a mechanism that shows up very often. Usually, events going out are assumed to be within expected range because it was generated within that component. Or maybe I’m misunderstanding the Vue.js mechanism.
Going Bigger
As mentioned earlier (in the context of state management) there’s an entire section dedicated to scaling up to larger and more complex projects. But we do get a preview of several mechanisms because they rightly belong in “Components In-Depth”
One item I had wondered about upon learning v-model was how that would work across component boundaries. The short version: Either (1) bind to a native input component inside the custom component template, or (2) bind to a writable computed value with getter and setter.
I think I have a good grasp of slots, but not scoped slots. It took me three readings to understand it is a (convoluted at first glance) way for information to cross parent/child component boundary. This will definitely take several rounds of hands-on practice (and painful debugging sessions) to master.
Symbols keys were brought up as a way to avoid Provide/Inject naming ambiguity in large projects because they potentially have global scope. This is the first I remember seeing symbol keys, I expect to get more information about it elsewhere in documentation.
Vue.js apps that grow big enough to worry about download size can split components off to be loaded asynchronously on demand. Vite recognizes this mechanism as a breakpoint for bundling purposes. I have yet to come across its Angular counterpart. Possibly lazy loading?
I went through a short tutorial on Vue.js site and found it to be a succinct overview. It doesn’t go very deep in any single topic, instead introducing a breadth of Vue concepts with links for deeper reading. The recommended step to follow that tutorial is the documentation section labeled “Essentials”. It was instructive reading and some items I found notable were:
HTML
Vue.js essentials logically started with Creating an Application where I was happy to learn HTML is at the forefront acting as I thought markup should. An Vue application instance mounts to an element on the page. This is in contrast to Angular (and what I understood of webpack) where JavaScript is at the forefront and primary job of index.html is to load that JavaScript. It has almost no content of its own.
Because of this, innerHTML markup on Vue-bound components can act as template for that component. Which is convenient, but I wonder if there’s a way to have fallback text to show the user before Vue loads. Or if Vue fails to load because the user turned off JavaScript in their browser.
Another effect of this architecture is that it’s valid to have multiple Vue applications on a single page, each mounted on a different HTML container element. I don’t think I can do this with Angular, which I’ve only ever seen control the entire page.
Or maybe I can mount no Vue application at all? I don’t understand the nuts and bolts yet, but it seems feasible to let the markup load quickly for the user to see. And sometime after that, mount Vue components as needed.
A downside of this approach is that Vue might have stuffed too much into the HTML file. One example is Vue dynamic arguments, which look just like HTML attributes to the browser and is liable to get coerced to lowercase by browser’s parser causing “name not found” errors.
Reactivity
The Reactivity Fundamentals section was the first Vue documentation section where I saw large differences between Vue’s ‘Options API’ and ‘Composition API’ variations. Using options API means letting Vue handle everything behind the scenes, but using composition means the author has to be aware of what goes on behind that curtain and have to correctly participate in the process.
A core part of Vue 3 is the use of JavaScript proxy around component data so Vue knows when data has changed and need to react accordingly. Some notable side effects are that this pointer behaves slightly differently. We should avoid arrow functions and avoid tracking state outside of data. If we inadvertently share static data across instances, that will become a source of problems.
The reactivity infrastructure leads to a significant difference between computed properties and methods. Computed values are updated only when their reactive dependencies (through their proxies) are updated. This allows performance optimizations like returning a cached value rather than running the computation again. In contrast, methods are always called.
Compared to Angular, Vue’s reactivity system is much more constrained in scope. Nothing like Angular’s use of RxJS, which was its own big sprawling thing.
Components
The Vue Essentials section ends with Component Basics which lived up to its name covering the very basics of Vue components. Enough for us to understand how the various concepts tie together, even if we don’t understand them in detail just yet. For those that want to get deeper, there are plenty of links for more details.
Communication between components and their parents are usually handled in one of three channels: (1) Hosts can set value on a component’s props. (2) Component can emit events to handlers on the host. (3) Host can send template fragment via slots. Hosts can dynamically control what components are loaded with <component> which seems like a very powerful tool, illustrated with a simple tabbed interface where each tab is a different component.
Compared to Angular, I didn’t see a mechanism for code to interact with components beyond parent-child relationships. The good news is that there’s no counterpart to the headache of Angular service registration and injection. The bad news is that I don’t know how to get similar functionality in Vue. [UPDATE: I found Vue’s Provide/Inject.]
Infrastructure
Vue Playground is used for live code examples that we can play with in the browser. It seems to be an openly available tool, I didn’t see any restrictions constraining it to Vue documentation examples. This is a very promising option for experimenting with Vue concepts hands-on later.
At several locations, there were links to video courses on Vue School. Normally, I’m not a fan of video instruction but perhaps I should at least try a few of their free courses to see how well I can learn. The bar is high: it needs to be pretty impressive for me to start paying a subscription!
After a brief detour exploring Vite’s support for legacy browsers like IE11, I returned to learn more about Vue.js from its own tutorial. Vue advertised itself as flexible in many ways and this is immediately visible on the first page of the tutorial: in the upper left corner we have two choices to make. Options vs. Composition API, and HTML vs. SFC format. Combined, it implies the tutorial can show us how to use Vue four different ways and would be a great resource for some direct comparisons.
SFC format integrate a component’s HTML template, CSS, and JavaScript/TypeScript all in a single *.vue file. These files are processed by a build-time tool like Vite to generate files actually going into a browser. In contrast, the HTML format is intended for using Vue without build tools. All of Vue is linked from the HTML and all script lives in the HTML as well for direct browser consumption.
Options API is the format used by Codecademy’s Vue.js course as well. It imposes a particular organization to Vue component data. Composition API does not impose such structure and so the JavaScript can be organized however you like but it also comes with requirement for managing overhead we wouldn’t have to worry about with Options API. In terms of expressive power, Options API is implemented using Composition API. Meaning anything we can do with Options we can do with Composition, but the reverse is not necessarily true.
For my first pass, I will leave things at default recommended for Vue beginners: Options API in SFC format. This tutorial is very short, with just 15 sections. Or more accurately 13 sections when accounting for the fact not much material is covered by the first page introduction or last page conclusion. The implementation structure is an in-browser learning environment similar in concept to Codecademy’s learning environment. The upside is that we don’t have to set up a local development environment, the downside is that we don’t get to see how a Vue development environment would look.
Vue’s tutorial covers a few important concepts with some overlap with Codecademy’s course (interpolation, directives, data/computed/method/watch) and some areas are different. It didn’t get into forms as Codecademy did, but did get into componentization, lifecycle hooks, and props/emits/slots which Codecademy’s course did not.
Both of those are shallower and more superficial than something like Angular’s “Tour of Heroes” tutorial, which went into far more depth starting with setting up a local development environment. If I want a Vue tutorial with that level of depth I will have to look elsewhere. Still, they were instructive and I’m glad I’ve gone through both Codecademy’s course and Vue.js tutorial. They prepared me to go deeper with Vue documentation starting with the “Essentials” section.
One tangential item I learned from this tutorial is the site https://jsonplaceholder.typicode.com/ for a publicly accessible free static mockup of generic API endpoints returning ipsum lorem data. This could come in handy for my own experiments in the future.
While looking over Vue.js’s Quick Start example, I noticed its default set of tools included Vite. I understand it plays a role analogous but not identical to webpack in Angular’s default tool set. I found webpack’s documentation quite opaque, so I thought I would try to absorb what I can from Vite’s documentation. I still don’t understand all the history and issues involved in JavaScript build tools, but I was glad to find Vite documentation more comprehensible.
The introductory “Why Vite?” page explained Vite takes advantage of modern browser features for JavaScript code modules. As a result, the client’s browser can handle some of the work that previously must be done on the developer machine via webpack & friends. However, that still leaves a smaller set of things better done up front by the developer instead of later by the client, and Vite takes care of them.
In time I’ll learn enough about JavaScript to understand what all that meant, but one section caught my attention. Given Vite’s focus on leveraging modern browsers, I was surprised to see “browser compatibility” section included an official plug-in @vitejs/plugin-legacy to support legacy browsers. Given my interest in writing web apps that run on my pile of old Windows Phone 8, this could be very useful!
I opened up my NodeJS test apps repository and followed Vite’s “Getting Started” guide to create a new project using the “vanilla TypeScript” template preset. To verify I’ve got it working as expected, I built and successfully displayed the results on a current build of Google Chrome browser.
Then I added the legacy plugin and rebuilt. It bloated the distribution directory up to 80 kilobytes, which is a huge increase but still almost a third of the size of a blank Angular app and quite manageable even in space-constrained situations. And most importantly: yes, it runs on my old Nokia Lumia 920 phone with Windows Phone 8 operating system. Nice! I’m definitely tucking this away in my toolbox for later use. But for right now, I should probably get back to learning Vue.
The first difference is here we’re creating an application with Vue.js, which means firing up command line tool npm init vue@latest to create an application scaffolding with select features. Since I’m a fan of TypeScript and of maintaining code formatting, I said yes to “TypeScript”, “ESLint” and “Prettier” options and no to the rest.
I then installed all the packages for that scaffolding with npm install and then I ran npm run build to look at the results in /dist/ subdirectory. They added up to a little over 60 kilobytes, which is roughly one-third built size of Angular’s scaffolding. This is even more impressive considering that several kilobytes are placeholders: about a half dozen markup files plus a few SVG files for vector graphics. The drastically smaller file sizes of Vue apps are great, but what have I given up in exchange? That’s something I’ll be looking for as I learn more about both platforms.
Poking around in the scaffolding app, I saw it demonstrated use of Vue componentization via its SFC (Single File Component) file format. A single *.vue file contained a component’s HTML, CSS, and TypeScript/JavaScript. Despite the fact they are all text-based formats and designed to coexist, I’m not a fan of mixing three different syntax in a single file. I prefer Angular’s approach of keeping each type in their own file. To mitigate confusion, I expect Vue’s editor tool Volar would help keep the three types distinct.
Some Vue components in the example are tiny like IconTooling.vue which is literally a wrapper around a chunk of SVG to deliver a vector-graphic icon. Others are a little more substantial like WelcomeItem whose template has three slots for information: #icon, #heading, and everything else. This feels quite different from how Angular components get data from their parents. I look forward to learning more about this style of code organization.
I’m far from an expert with the Angular web app framework, but I’m itching to look around. Use what I’ve learned of Angular as a baseline to compare design tradeoffs against those made by other web app frameworks. I thought Vue.js was worth a look, and I’ll start with Codecademy’s “Learn Vue.js” course. It was very short and really didn’t cover very much of Vue.js at all.
The good news is that learning Angular helped introduce many web app framework concepts, making this Vue.js lesson easier to understand despite its short whirlwind tour format. When it came to Vue directives, I can immediately see similarities between Vue’s “v-if” and Angular’s “#ngIf“. v-for and #ngFor, etc. A novelty to me was the concept of directive modifiers which are shorthand for calling common methods. v-on:submit.prevent is an event handler for a form “submit” event, and appending “.prevent” means Vue will also call Event.preventDefault(). Something many event handlers would do but, with the modifier, they won’t have to explicitly do so.
One area this course skipped, probably in the interest of keeping things simple, was by using Vue as a single monolithic CDN-delivered package. Bypassing the entire build/bundle process. Initially I thought “yikes” at how large the result must be. Until I looked at the download size of vue.global.prod.js and saw all of Vue weighed in at just 128 kilobytes, almost half the size of a tree-shaken, minified, optimized production build of Angular app boilerplate. And it can be further GZip-compressed down to 48 kilobytes for space-constrained places like ESP8266 flash memory. OK, that’ll work!
Half the course (two out of four sections) focused on building forms with Vue. This was unfortuate for me personally because I never really dug into doing forms with Angular, so I couldn’t make a direct comparison between those two frameworks. I read enough about forms in Angular to learn that there were two different ways to do it. I didn’t know enough to choose between them, so I never did either.
Still, building forms allowed us to cover a lot of general ground about using Vue. It let us see how Vue wanted our code to be organized in one gigantic object passed into the constructor. (I would later learn this was the “Options API” approach, the alternative “Composition API” was not covered in this Codecademy course.) We have data properties, computed properties that calculte based on data, watchers to act in response to data changes, and methods to for everything else not directly llinked to a property. It seems like a better structure than a wide-open JavaScript class, especially for components with a tight focus.
The fourth and final section covered doing CSS in Vue. I was quite wary of this section, as I’ve read some complaints about CSS delivered by JavaScript code at runtime. It means the browser rendering engine has no opportunity to preview and preprocess those CSS rules before the JavaScript code inserts them, a pattern which can have disastrous impact on rendering performance. What this Vue.js course covered isn’t quite the full-fledged “CSS-in-JS” (which has its own Codecademy course) but I’d still be cautious of using v-bind:style. On the other hand, v-bind:class seems like less of a danger. In this approach, the browser gets to process CSS beforehand and we’re toggling application on and off via JavaScript code. I’m more inclined to go with v-bind:class.
And finally, I was looking forward to seeing how Vue handled componentization and I was very disappointed to see it was considered out of scope of this course. I think it’d be instructive to see how Angular components compared to Vue components, maybe see how they compare to LitElement, and how they compare to standard web components. Well, I won’t find any of those answers here because the course mentioned componentization as being very useful and never got into how to do so! I’ll have to look elsewhere for that information.
Having an old Windows Phone 8 die (followed by dissection) was a fresh reminder I haven’t put enough effort towards my desire to “do something interesting” with those obsolete devices. The mysterious decay of one device was a very final bell toll announcing its end, but the clock is ticking on the rest of them as well. Native app development for the platform was shut down years ago, leaving only the browser as an entry point. But even that browser, based on IE11, is getting left further and further behind every day by web evolution.
In one of my on-and-off trips into web development, I ran through Angular framework tutorial and then added legacy project flags to make an IE11-compatible build I could run on a Windows Phone 8. That is no longer possible once Angular dropped support. One of the reasons I chose Angular was because it was an “everything included, plus the kitchen sink” type of deal. An empty Angular app created via its “ng new” command included all the tools already configured for their Angular defaults. I knew the concepts of tools like “bundler”, “minimizer”, etc. but I didn’t know enough to actually use them firsthand. Angular boilerplate helped me get started.
But the reason I chose to start with Angular is also the reason I won’t stay with it: the everything framework is too much framework. Angular targets projects far more complex and sophisticated than what I’m likely to tackle in the near future. Using Angular to create a compass web app was hilarious overkill where size of framework overhead far exceeded size of actual app code.
In my search for something lighter-weight, I briefly looked into Polymer/Lit and decided I overshot too far into too little framework. Looking around for my Goldilocks, one name that has come up frequently in my web development learning is Vue.js. It’s supposed to be lighter and faster than Angular/React but still have some of the preconfigured hand-holding I admit I still need. Maybe it would offer a good middle ground and give me just enough framework for future projects.
One downside is that current version Vue 3 won’t run on IE11, either. However, the documentation claimed most Vue fundamental concepts haven’t changed from Vue 2, which does support IE11 and is still on long-term service status until the end of 2023. Maybe I can get started on Vue 3 and write simple projects that would still run on Vue 2. Even if that doesn’t work, it should help orient me in a simpler setup that I could try to get running on Windows Phone 8.
I’m cautiously optimistic I can learn a lot here, because I saw lots of documentation on Vue project site. Though that is only a measure of quantity and not necessarily quality. It remains to be seen whether the material would go over my head as Lit’s site did. Or if it would introduce new strange concepts with a steep learning curve as RxJS did. I won’t know until I dive in.
I enjoyed exploring leading edge web development with experimental features like magnetometer API and evolving standards like PWA. But learning about the trailing edge also has some value for me. I have a stack of old Windows Phone 8 devices. Microsoft had shut down native app development for the platform as part of its end-of-life treatment, leaving its onboard web browser as the only remaining entry point. Based on Internet Explorer 11, support of which has been dropping from platforms left and right, there’s definitely a clock ticking away if I want to be able to do anything with those phones.
Assuming, of course, those phones don’t decay and die on their own like this Nokia Lumia 520 has done. It’s been a guinea pig to test things like ESA’s ISS tracker web app. When I turned it on recently, it failed to boot and crashed to this blue screen of death. Unlike its desktop Windows equivalent, there are no debug information printed onscreen. Documentation has been purged from Microsoft and Nokia websites as they have disowned these devices. So, it was up to iFixit to preserve documentation on performing factory reset with a hardware key sequence: From powered off state, hold [volume down] and press [power] to start phone. As soon as phone vibrates, release [power]. Once phone boots to exclamation mark, release [volume down]. Press key sequence [volume up], [volume down], [power], [volume down]. Watch spinning gear onscreen for a few minutes.
But performing such a reset on this phone didn’t help, I just ended back at the sad faced blue screen of death. I don’t know what happened to this phone. I hadn’t thought electronics would decay with time, but something on this one has failed in a way I lacked information or tools to diagnose. I powered up my remaining Windows Phones and they were able to boot, so it’s not a common/widespread failure mode. (Yet?) In any case, today this dead phone gets the teardown treatment.
Nokia Lumia 520 was a simple and basic entry-level phone, dating back to the era when batteries were easily accessible and removable by the user. Not so much anymore, which is sad though there are occasional encouraging signs. Popping off the easily-removed blue back cover, we see physical features like a microSD card slot, SIM slot, and headphone jack. All useful features disappearing from modern phones.
The next layer is a black plastic cover held by multiple Torx fasteners and plastic clips. Removing that cover exposes phone mainboard, where we can see the thickest component is the rear-facing camera. It actually sits in the middle of a hole cut out of the circuit board, protruding both in front and behind of the board. (Lumia 520 does not have a front-facing camera.)
Ribbon cable near the top of the device is for touch digitizer input via this Synaptics chip.
Synaptics
S22028
33120155
ACAN310
Sadly, a web search with engraved text failed to return anything useful.
The touch controller communicated with the rest of the phone with ten wires, but they are far too fine-pitched for my current skill level to work with.
It’s a similar story with the LCD, connected to mainboard with twenty wires. Far too few to directly control an 800x480x3 LCD array, these must be data buses communicating with a controller somewhere downstream. At least six of these wires visibly hint at differential signal pairs.
Front and back views of removed mainboard. Full of tiny components, most of which hidden under RF shields, I see only two components (battery connector and vibration motor) that I could realistically repurpose.
With mainboard removed, I see no further fasteners to remove, and no obvious seams.
I knew it was too complex to be a single piece, so I manually twisted the assembly looking for signs of seams between parts. Attacking candidates with iFixit picks allowed me to separate the front panel touch digitizer from display subassembly.
The display assembly is held to its chassis frame with a few strips of adhesives and could be carefully peeled apart.
Freed from its structural frame, the display assembly feels very delicate. It easily flexes and twists to reveal details like these side-emitting LEDs for backlight illumination.
Peeling back foil tape uncovered ultra fine-pitched LCD array control wires embedded between layers of glass.
Trying to separate LCD array from backlight, I unfortunately cracked the glass and destroyed the LCD. I might be able to reuse the LED backlight but it’s going to be a serious challenge finding (and soldering to) those fine wires for LED power.
Goodbye, Nokia Lumia 520. I’m sad I didn’t get around to finding something interesting to reuse you as a whole unit. And your component parts are mostly too tiny for my current skill level to work with. But your death gave me a kick in the pants to get on with my studies. I hope to make use of your surviving contemporaries.
The full scope of PWA (Progressive Web App) is too much for me to comprehend with my limited web development skills. Fortunately, I don’t need to. PWA helper libraries exist, including some tailored to specific web frameworks. Reading over a developer guide for Angular framework’s PWA helper, I understood it offers basic PWA service worker functionality without much effort. Naturally, I had to give it a try and see how it turns my Compass project, a simple practice Angular web app, into a PWA.
Following instructions, I ran “ng add @angular/pwa” in my project and looked over the changes it made. As expected, there were several modifications to configuration JSON files on top of adding one of their own ngsw-config.json. Eight placeholder icon PNG files (showing the Angular logo) were added to the source tree, each representing a different resolution from 72×72 up to 512×512. Notably, the only TypeScript source code change was in app.module.ts to register service worker file ngsw-worker.js, and that service worker file is not part of the source tree where we can enhance it. And if there’s an extension mechanism, I don’t see it. This tells me behavioral changes are limited to what we can affect via settings in ngsw-config.json. For app authors that want to go beyond, they’d have to write their own PWA service worker from scratch.
But I don’t have any ambition of code changes in this first run. I replaced those placeholder icon PNG files. Change them from the Angular logo to a crude compass I whipped up in a few minutes. Plus a few edits to the application name and cosmetic theme color. (Duplicated in two locations: the .webmanifest file and in index.html.) I built the results, deployed to GitHub Pages, and brought it up on my Android phone Chrome browser to see if anything looks different.
Good news: Android Chrome recognized the site is available as a PWA and offered the option to install it. (Adding yet another browser interface bar to the clutter…) I clicked install, and my icon in all its crudeness was added to my Android home screen.
Bad news: I tapped the icon, saw the loading splash screen (with an even bigger version of my crude icon in the middle) and then… nothing. It was stuck at the splash screen.
I killed the app and tried a second time, which was successful: my compass needle came up on screen after a brief pause. (1-2 seconds at most.) I returned to home screen, tapped the icon again, and it came up instantly. Implying the app was suspended and resumed.
A few hours later, I tapped the icon again. It was apparently no longer suspended and no longer available for resume, because I saw the splash screen again and it got stuck again. Killing the app and immediately trying again was successful.
The observed pattern is thus: Initial launch would get stuck at the splash screen due to an undiagnosed issue. Killing the stuck app and immediately relaunching would be successful. Suspend and resume works, but if a suspended app is terminated, we’re back to the initial launch problem upon relaunch.
Since it wasn’t possible to change any code in @angular/pwa I’m not sure what I did wrong. Following instructions for “Debugging the Angular service worker” I headed over to the debug endpoint ngsw/state where I saw it couldn’t download favicon.ico from the server. (Full debug output error message below.) I could see the file exists on the server, so I don’t understand why this PWA service worker could not download it. At the moment I’m at a loss as to how to diagnose this further. For today I’m going to declare a partial success and move on.
[UPDATE: I’ve figured it out. By default the PWA service worker assumes the app was deployed to server root, which in this case was incorrect. Since it is hosted at https://roger-random.github.io/compass/ I needed to run “ng deploy --base-href /compass/“]
NGSW Debug Info:
Driver version: 15.2.8
Driver state: EXISTING_CLIENTS_ONLY (Degraded due to failed initialization: Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico)
Error: Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico)
at PrefetchAssetGroup.cacheBustedFetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:479:17)
at async PrefetchAssetGroup.fetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:449:19)
at async PrefetchAssetGroup.fetchAndCacheOnce (https://roger-random.github.io/compass/ngsw-worker.js:428:21)
at async https://roger-random.github.io/compass/ngsw-worker.js:528:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9)
Latest manifest hash: ecc53033977c5c6ddfd3565d7c2bc201432c42ff
Last update check: 37s199u
=== Version ecc53033977c5c6ddfd3565d7c2bc201432c42ff ===
Clients:
=== Idle Task Queue ===
Last update tick: 42s670u
Last update run: 37s668u
Task queue:
Debug log:
[37s320u] Error(Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico), Error: Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico)
at PrefetchAssetGroup.cacheBustedFetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:479:17)
at async PrefetchAssetGroup.fetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:449:19)
at async PrefetchAssetGroup.fetchAndCacheOnce (https://roger-random.github.io/compass/ngsw-worker.js:428:21)
at async https://roger-random.github.io/compass/ngsw-worker.js:528:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9) Error occurred while updating to manifest 271c8a71bf2acb8075fc93af554137c2e15365f2
[37s229u] Error(Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico), Error: Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico)
at PrefetchAssetGroup.cacheBustedFetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:479:17)
at async PrefetchAssetGroup.fetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:449:19)
at async PrefetchAssetGroup.fetchAndCacheOnce (https://roger-random.github.io/compass/ngsw-worker.js:428:21)
at async https://roger-random.github.io/compass/ngsw-worker.js:528:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9) initializeFully for ecc53033977c5c6ddfd3565d7c2bc201432c42ff
[37s110u] Error(Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico), Error: Failed to retrieve hashed resource from the server. (AssetGroup: app | URL: /favicon.ico)
at PrefetchAssetGroup.cacheBustedFetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:479:17)
at async PrefetchAssetGroup.fetchFromNetwork (https://roger-random.github.io/compass/ngsw-worker.js:449:19)
at async PrefetchAssetGroup.fetchAndCacheOnce (https://roger-random.github.io/compass/ngsw-worker.js:428:21)
at async https://roger-random.github.io/compass/ngsw-worker.js:528:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9
at async https://roger-random.github.io/compass/ngsw-worker.js:519:9) Error occurred while updating to manifest 271c8a71bf2acb8075fc93af554137c2e15365f2
Reading through web.dev’s “Learn PWA!” guide was quite a lot of information to absorb, much of which has already leaked out of my brain. I’d need a lot more practice at designing and building web applications before I can understand and retain the full range of PWA capabilities. Fortunately, we don’t need to understand everything. We can start small, leveraging libraries that expose commonly desired functionality. There are general PWA libraries like Workbox, and most popular web app frameworks also have an optional PWA library. Angular’s take is introduced in “Service Workers and PWA“, one of Angular’s Developer Guides.
I was relieved to learn that Angular 15’s @angular/pwa library was exactly the “start small” option I had hoped for. Installing the library adds a service worker implementation of fundamental service worker tasks: cache the bundles and assets of an Angular app, serves them as needed while running as PWA, and updates those files if newer versions are found on the server. This is a small subset of all the features I saw covered in “Learn PWA!” but it’s enough to turn an Angular app into a PWA!
Underscoring the potential complexity of PWAs, the longest page in this Angular PWA developer guide is “Service worker in production” which talks about all the ways PWAs can go wrong and how an Angular developer can fix it. It’s possible the author of this guide just wants to be through, and I certainly appreciate honesty and detail. Much better than optimistically assuming nothing will go wrong. Still, seeing all the ways things can go wrong isn’t terribly confidence inspiring for a beginner.
If an Angular web app author wants to go beyond this basic service worker, I’m not sure if it’s built to be extensible or if said author is expected to write their own service worker from scratch. The developer guide didn’t say anything either way. Also unknown to me is whether trying to use a library like Workbox would risk collisions with the default service worker implementation. I saw no warnings about that, either.
Every browser imposes their own user interface cluttering up the screen, a fact of life for all web apps. This clutter motivated me to give my Compass web app a full screen option to get rid of those browser interface elements. It’s pretty good, but it requires the user to press that “Go Fullscreen” button. What if a web app can launch in full screen state, just like native apps? That’s one of many “native app like” upsides to a PWA (Progress Web App) so I started reading “Learn PWA!” published by the Google Chrome developer relations team at web.dev. The technology has promise but today’s landscape has a lot of differences across browser support that make a developer’s life difficult.
A core component of PWA is the “service worker”, a chunk of JavaScript associated with a web app but runs behind the scenes separately from the rest of web app code. It has the ability to intercept all network requests of a web app, acting like a web server to the rest of the web app but running without a server. This makes it possible for a PWA to run without a network connection, just like native apps could. Making server-like behavior possible are a series of technologies, most of which are usable independent of PWAs.
The service most surprising to me was IndexedDB API, a structured storage database available to web apps across most modern browsers. It is a NoSQL database along the lines of MongoDB, except running entirely within the browser. Wow! And here I thought web apps were still limited to cookies for data storage. However, like cookies, data stored in IndexedDB can be discarded by the user at any time. Therefore it is necessarily limited to short-term storage scenarios. Like a PWA running offline, tracking data to be uploaded to the server once connectivity is restored.
Since service workers run behind the scenes, outside the life cycle and scope of standard web app code, it can get tricky to debug problems. An entire section Tools and Debug is dedicated to discussion about it. Some of the tips are useful beyond PWAs, like port forwarding from development desktop to Android physical devices. I think this would have helped me with my Compass web app, with its device-specific hardware.
Speaking of device-specific behavior, PWA offers the promise of a single codebase that can run across multiple devices, but this course is realistic about the fact that current browser implementations vary widely and require platform-specific knowledge. This gets worse the further we get away from core behavior. In the Enhancements section, there were more about how iOS Safari differs from Android Chrome than there were about behavior they had in common. What a mess.
From this “Learn PWA!” guide I’ve learned that PWAs start with a relatively simple core set of features (caching and network call interception) but can get very complex very quickly for ambitious developers who want to advanced features. Going hand-in-hand with that power is the potential for things to go very, very wrong. The one that made me grimace is the fact a misbehaving service worker could intercept F5 (refresh) preventing us from loading updated code. Aack! There’s a lot to learn beyond this guide. The web.dev team offers a six-part hands-on PWA training course to supplement this guide, and of course there are other websites out there with additional resources.
It seems to me that PWA & related API were designed to offer maximum flexibility. Which is why a web app author is responsible for their own service worker so they can do exactly what they need in the context of their web app. However, in practice most web apps will be very similar. In order to minimize people copying and pasting code from random StackOverflow pages, the Chrome team has built the Workbox library to deliver code implementing popular behavior in a single library. This is useful. Furthermore, many web application frameworks have their own libraries for turning their applications into PWAs. Given my brief exposure to Angular, my next stop is to read up on Angular’s PWA library.
I’ve spent a few hours each on Proxmox VE and TrueNAS SCALE, the latter of which is now hosting both my personal media collection and a VM-based Plex Media Server view of it. Proxmox and TrueNAS are both very powerful pieces of software with a long list of features, a few hours of exposure barely scratched the surface. They share a great deal of similarities: they are both based on Linux, they both use KVM hypervisor for their virtual machines, and they both support software-based data redundancy across multiple storage drives. (No expensive RAID controllers necessary.) But I have found a distinct difference between them I can sum up as this: TrueNAS SCALE is a great storage server with some application server capabilities, and Proxmox VE is a great application server with an optional storage server component.
After I got past a few of my beginner’s mistakes, it was very quick and easy to spin up a virtual machine with Proxmox interface. I felt I had more options at my disposal, and I thought they were better organized than their TrueNAS counterparts. Proxmox also offers more granular monitoring of virtual machine resource usage. With per-VM views of CPU, memory, and network traffic. My pet feature USB passthrough allows adding/removing USB hardware from a virtual machine live at runtime under Proxmox. Doing the same under TrueNAS requires rebooting the VM before USB hardware changes are reflected. Another problem I experienced under TrueNAS was that my VM couldn’t see the TrueNAS server itself on the network. (“No route to host”) I worked around it by using another available Ethernet port on my server, but such an extra port isn’t always available. Proxmox VM could see their Proxmox host just fine over a single shared Ethernet port.
I was able to evaluate Proxmox on a machine with a single large SSD that hosts both Proxmox itself and virtual machines. In contrast, TrueNAS requires a dedicated system drive and additional separate data storage drives. This reflects its NAS focus (you wouldn’t want to commingle storage and operating data) but it does mean evaluating TrueNAS requires a commitment at least two storage devices versus just one for Proxmox.
But storage is easy and (based on years with TrueNAS CORE) dependable and reliable with redundant hardware. This is their bread-and-butter and it works well. In contrast, data storage in Proxmox is an optional component provided via Ceph. I’ve never played with Ceph myself but, based on skimming that documentation, there’s a steeper learning curve than setting up redundant storage with TrueNAS. Ceph seems to be more powerful and can scale up to larger deployments, but that means more complexity at the small end before I can get a minimally viable setup suitable for home use.
My current plan is to skip Ceph and continue using TrueNAS SCALE for my data storage needs. I will also use its KVM hypervisor to run a few long-running virtual machines hosting associated services. (Like Plex Media Server for my media collection.) For quick experimental virtual machines who I expect to have a short lifespan, or for those that require Proxmox specific feature (add/remove USB hardware live, granular resource monitoring, etc) I’ll run them on my Proxmox evaluation box. Over the next few months/years, I expect to better able evaluate which tool is better for which job.
After trying and failing to use the default method to run Plex Media Server via TrueNAS SCALE’s “App” catalog, I’m falling back to a manual route: spinning up a virtual machine with Ubuntu Server 22.04 to run Plex Media Server with my own preferred settings. I suppose I could learn about Helm charts so I could write one to run Plex my way, but at the moment I’m not too motivated to do so.
Recently I had been running Plex in a Docker container, which resolved my old gripes about FreeNAS plug-ins and FreeBSD freshports versions of Plex falling out of date. Plex developers maintain the container themselves and it gets updated in sync with official releases. A really nifty feature of their Docker container is that it doesn’t really have Plex in it: it has code to download and run Plex. In order to pick up an updated version of Plex, I don’t have to pull a new container. I just have to stop and restart it, and it downloads the latest and starts running it.
One subtlety of running Plex in Docker is the warning I shouldn’t use a mapped network drive for server configuration data. I had thought it would be a good way to keep my Plex database constantly backed up on a TrueNAS ZFS redundant drive array, but I abandoned that plan after reading a scary disclaimer on the Docker repository README: “Note: the underlying filesystem needs to support file locking. This is known to not be default enabled on remote filesystems like NFS, SMB, and many many others. The 9PFS filesystem used by FreeNAS Corral is known to work but the vast majority will result in database corruption. Use a network share at your own risk.“
I could install Docker Engine in my virtual machine for Plex, and repeat my configuration, but it seems weird to have nested virtualization mechanisms. (Docker inside KVM.) So this time I will run Docker as a service installed to my virtual machine, installed from Plex-maintained official repository. Migrating my Plex database started by finding the correct directory in both my existing Docker container volume and my new Ubuntu Server virtual machine at “/var/lib/plexmediaserver/“. Copying the files directly was not sufficient (Plex Media Service would fail to launch) because I forgot to update file permissions as well. That was fixed by running “chown -R plex:plex Library” on the library database directory tree.
One unexpected obstacle is that a VM running under TrueNAS SCALE couldn’t see the server itself. Doesn’t matter if I’m trying to use NFS mapping, SSH, or HTTP, the server address is unreachable and times out with the error “No route to host”. I confirmed this is not a general KVM issue, as my Ubuntu Desktop laptop with KVM and Proxmox VE had no such problems. I hypothesize it has something to do with how TrueNAS SCALE configured network bridge for virtual machines, but I don’t know enough Linux networking to know for sure. As a workaround, I could bind my virtual machine to the Realtek chipset Ethernet port integrated on my server’s motherboard. TrueNAS runs on an Intel NIC (network interface card) because FreeNAS didn’t support the Realtek onboard port years ago. Now under TrueNAS SCALE I have access to both ports, so I run the TrueNAS server on the Intel NIC and bind my virtual machines to motherboard onboard Ethernet. Not the most satisfying solution, but it uses what I have on hand and is good enough.
In addition to simple and reliable data storage capabilities, FreeNAS/TrueNAS also has an application services side. I’ve had a bumpier relationship with those features. Among the data stored on my TrueNAS box is my personal music collection, and I’ve been using Plex as one of several ways I consume my media. Plex Media Server has thus been my guinea pig for TrueNAS application hosting and finding problems with each. I’ve tried using it as a FreeNAS Plugin (stale and infrequently updated), as a self-managed application inside a FreeBSD jail (less stale but still a significant delay), to a Docker container running inside an Ubuntu virtual machine (timely updates but bhyve hypervisor has problems with Ubuntu) and now that I’ve migrated to TrueNAS SCALE I have new options to try.
Plex Media Server is one of the options under TrueNAS SCALE’s “Apps” menu representing a collection of Helm charts. I have some vague idea this mechanism is related to Kubernetes, but I haven’t invested time into learning the details as I just wanted to use it as a point-and-click user. I tried to create an instance with “Enable Host Path for Plex Data Volume” option pointing to my existing media share. My attempt failed with the following error:
Error: [EINVAL]
chart_release_create.app
VolumeMounts.data.hostPathEnabled.hostPath: Invalid mount path.
Following service(s) use this path: SMB Share, NFS Share
A bit of web searching found this is expected behavior: in order to avoid any potential problems with conflicting operations, helm charts will verify that each app has exclusive control to all volumes before proceeding. This is a reasonable thing to do for most applications, but unnecessarily cautious for media data on a read-only network share. And furthermore, it is not compatible with my media consumption pattern which requires leaving SMB and NFS sharing running. Thus, I add “Plex Helm Chart” to the running list of TrueNAS application service I’ve tried and failed.
I will now try a different approach: create a virtual machine for running Plex.