{"id":21205970,"url":"https://github.com/eirikt/default-webapp-vue-vite-vercel","last_synced_at":"2026-04-11T21:44:49.196Z","repository":{"id":260934760,"uuid":"882740355","full_name":"eirikt/default-webapp-vue-vite-vercel","owner":"eirikt","description":"My opinionated setup for web application development, 2024 edition.","archived":false,"fork":false,"pushed_at":"2025-01-12T09:47:01.000Z","size":528,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-21T15:31:43.589Z","etag":null,"topics":["vercel","vite","vue"],"latest_commit_sha":null,"homepage":"https://defaultwebapp.vercel.app","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eirikt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-03T16:29:58.000Z","updated_at":"2025-01-12T09:47:05.000Z","dependencies_parsed_at":"2024-11-03T18:24:45.484Z","dependency_job_id":"2c3e3ae2-8dd7-4a5d-9cac-588513c5e253","html_url":"https://github.com/eirikt/default-webapp-vue-vite-vercel","commit_stats":null,"previous_names":["eirikt/default-webapp-vue-vite-vercel"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eirikt%2Fdefault-webapp-vue-vite-vercel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eirikt%2Fdefault-webapp-vue-vite-vercel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eirikt%2Fdefault-webapp-vue-vite-vercel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eirikt%2Fdefault-webapp-vue-vite-vercel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eirikt","download_url":"https://codeload.github.com/eirikt/default-webapp-vue-vite-vercel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243658270,"owners_count":20326467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["vercel","vite","vue"],"created_at":"2024-11-20T20:53:52.416Z","updated_at":"2026-04-11T21:44:44.153Z","avatar_url":"https://github.com/eirikt.png","language":"Vue","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Default Webapp 2024\n*Vercel/Vite/Vue/TypeScript/Tailwind*\n\nMy default (opinionated/favourite) setup for web application development in 2024—a single-page application (SPA) with client-side rendering, to be more precise.\nAlso, I believe this setup is rather ordinary/idiomatic.\n\nThis is first and foremost a personal write-up.\nYet, it will (maybe) end up like a \"web project starter\",\na well-documented starting-point for a project, like a boilerplate—or just a basic tutorial.\nI will take a somewhat naïve approach when growing this codebase—arguing for and introducing features and tooling on the way as we go along, commit by commit, statement by statement.\n\nA live version is hosted on Vercel, as:\n\u003ehttps://defaultwebapp.vercel.app\n\n...\n\nThis repository/project is yet another iteration of previous \"default webapp\" projects:\n- https://github.com/eirikt/default-webapp-elm-now (2018)\n- https://github.com/eirikt/default-webapp-heroku (2014-2015)\n\nIt's a kind of personal code kata.\n\n\n\n## Prerequisites\nTo be very basic and specific, the only prerequisites are:\n- Some knowledge of English 😉\n- A computer, a steady supply of electric power, and network connectivity 😉\n- For the hosting service; an email account and an email client, signed in.\n- Some programming experience, just some\n\n\n### Project name\nFirst and foremost your project needs:\n1. an official (natural language) name\n\nThen, also create a:\n2. `package.json` \"name\"\n\n   *The \"name\" field contains your package's name and must be lowercase without any spaces.\n   It may contain hyphens, dots, and underscores.*\n\n3. Source-code-friendly name variant with no whitespace characters.\n\n4. URL-friendly name variant with no whitespace characters. Lowercase characters only (hyphens allowed).\n\n5. A full URL/domain name/address\n\nFor this little web application it is:\n1. Default Webapp 2024\n1. `default-webapp-2024`\n1. `defaultWebapp2024`\n1. `defaultwebapp`\n1. `defaultwebapp.vercel.app`\n\nFor a real web app, a personal domain name is (close to) mandatory.\nWe won't do that as part of our setup, at least not yet.\nAnyway, the routines are described here:\nhttps://vercel.com/docs/projects/domains/\n\n\n### Code editor\nGet a code editor, e.g. [Visual Studio Code](https://code.visualstudio.com), or one of JetBrains' editors.\n\nOk, let's publish a web app.\n\n\n\n## Initiate project development\nWe will use PowerShell in this little web project bootstrapping walk-through.\n(It won't be any big differences; in a POSIX shell, or even in \u003ckbd\u003eCMD\u003c/kbd\u003e.)\n\nOn a Windows PC, open PowerShell terminal by typing:\n\u003ckbd\u003eWIN\u003c/kbd\u003e+\u003ckbd\u003epowershell\u003c/kbd\u003e\n\n\n### pnpm\n[pnpm](https://pnpm.io) is a new take on the default Node package manager, **npm**.\nIt is especially the dependency management that is the motivation for using pnpm instead of npm.\nAlso, it is a one-stop-shop when it comes to sourcing in all required tools and dependencies into our project.\n\nCheck pnpm availability:\n```shell\npnpm\n```\nIf needed, install pnpm:\n```shell\nInvoke-WebRequest https://get.pnpm.io/install.ps1 -UseBasicParsing | Invoke-Expression\n```\nThen open up a new terminal window.\n\nCheck version, and while at it, update `pnpm`:\n```shell\npnpm --version\npnpm self-update\n```\n\n\n### Node.js\nCheck [Node.js](https://nodejs.org) availability:\n```shell\npnpm env list\nnode --version\n```\n\nIf needed, install Node.js, under pnpm management:\n```shell\npnpm env use --global lts\n```\nHere we are installing Node.js LTS (long-term support) version.\n\n\n---\n\n\nNow, let's get started.\nUsing your newly established project name (from above), create your project folder.\nGo to project folder.\n\nThe default \"build\" folder for Vite is `dist`, so we will establish that routine from the get-go.\nOur first \"build step script\" will be quite simple:\n```shell\nNew-Item dist -Type Directory\n```\n...with, as of now, no content to deploy.\n\nNevertheless, the very first thing to do, is getting our new webapp online;\ndeploying the most basic version imaginable of our webapp to our hosting service of choice.\nLet's start.\n\n\n\n## Set up hosting service\nWe will use [Vercel](https://vercel.com) as a deployment service and hosting partner.\nOther options are:\n- [GitHub Pages](https://pages.github.com)\n- [Netlify](https://www.netlify.com)\n- [Cloudflare](https://www.cloudflare.com)\n- [Fly](https://fly.io)\n- [Railway](https://railway.app)\n- [Render](https://render.com)\n\n...and many more.\n\nInstall Vercel client as a project-local, development-only dependency:\n```shell\npnpm install vercel --save-dev\n```\n\n_Optional:_ Check version:\n```shell\npnpm vercel --version\n```\n\n_Optional:_ Check if you are signed in, and have other projects at Vercel already:\n```shell\npnpm vercel project list\n```\nIf not yet signed in, cancel the routine, or choose e.g., the \"Continue with Email\" option.\nThis will lead you to signing in (and creating an account at Vercel if you not already have one).\nThe default account plan is \"Hobby\", and is free of charge.\nSee https://vercel.com/pricing/\n\nSign in, if not already signed in:\n```shell\npnpm vercel login \u003cemail address\u003e\n```\n\nThen go straight to deploying the very first \"version\" of your web page:\n```shell\npnpm vercel\n```\nState your project name; in our case: \"defaultwebapp\".\nSet the directory where the code is located, to: \"`./dist`\".\nOtherwise, stick to default options.\n\nIf you click on the \"Production\" link, it will respond with a `404 Not Found` as it is nothing there.\nWe need content...\n\n\n#### `index.html`\nAn `index.html` file is the default bootstrapping mechanism for both presenting a web page,\nand for loading a web application.\n\nCreate an `index.html` file:\n```shell\nNew-Item index.html\nCopy-Item .\\index.html .\\dist\n```\nRedeploy, directly to production:\n```shell\npnpm vercel --prod\n```\n\nHave a look:\nhttps://defaultwebapp.vercel.app\n\nNow, the `404 Not Found` has turned into a `200 OK`.\nWe're on the right path, but we still need content.\nLet's start with a \"Hello World!\".\n\n\u003csub\u003e[ \u003ccode\u003ev0.0.1-SNAPSHOT\u003c/code\u003e: [deployment](https://defaultwebapp-rlqbb0j1v-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### Hello World!\nEvery web app, no, wait–every information system should start with a \"Hello World!\" version!\n\nUpdate the `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value \"Hello World!\"\nCopy-Item .\\index.html .\\dist\n```\nRedeploy, and have a look.\n\nYes, we are live 👍\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is now (already) on 117 MB–due to the Vercel dependency\u003cbr/\u003e\n- Web page size (at rest): 14 B\u003cbr/\u003e\n- Web page size (network): ~125 B\u003cbr/\u003e\n- Web page response time: ~50-100 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.0.1-SNAPSHOT\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/54d6f1506b7dc88dcea6d2f7ccbeda6573f6f9de) | [deployment](https://defaultwebapp-44h74et6j-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n\n## Set up development environment\n\n\n### Version control, Git\nParamount for all software development is _version control_.\nWe will use [version control system](https://en.wikipedia.org/wiki/Version_control) [Git](https://en.wikipedia.org/wiki/Git), still the idiomatic choice after 15-20 years.\nGit is an open [distributed version control system](https://en.wikipedia.org/wiki/Distributed_version_control), and has many [SaaS](https://en.wikipedia.org/wiki/Software_as_a_service) providers,\ne.g., the prominent [GitHub](https://github.com).\n\n[Install](https://git-scm.com) Git.\n\nCreate a repository in GitHub, and clone the repository.\n\nAlternatively, create a local Git repository:\n```shell\ngit init\n```\n\nWe don't want to put all folders and files in under version control.\nA `.gitignore` file was created during the initial Vercel usage above.\nAdd some more paths and files to it.\nLooking a bit ahead of ourselves, we may e.g., ignore:\n```\n# Always ignore\n.ssh\n\n# Ignore generated/derived/downloaded stuff\n/dist\n/node_modules\n\n# Ignore IDE/tools stuff\n/.idea\n/coverage\n\n# Ignore other development stuff\n/.vercel\nLOG.md\n```\n\n\n### EditorConfig, for common IDE behaviour\nA nice (beloved) text editor (or a full-blown, hopefully not bloated, integrated development environment (IDE)),\nis an integral part of a software developer's life.\nWe are restricting individual preferences to a certain degree in this setup,\nbut the IDE is personal and should be free of choice.\nFor \"organizational scaling\" concerns, we are creating a common ground for IDEs with EditorConfig, IDE-agnostic editor configurations.\nThat consolidates, e.g., issues like whitespace handling—important for avoiding unnecessary version differences.\nSo, the text editors/IDEs used by project developers should support EditorConfig internally or via a plugin.\nMost commonplace text editors/IDEs have support for EditorConfig.\n\nCreate the `.editorconfig` file:\n```shell\nNew-Item .editorconfig\n```\n\nAdd, e.g., the following editor configurations:\n```\n# This file is for unifying the coding style for different editors and IDEs\n# EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\nend_of_line = crlf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n```\n\n...\n\n\nFor the next version, let's update the silly \"Hello World!\" with something a bit better;\na little project mission statement.\n\nUpdate `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value \"Default Webapp 2024 (v0.0.1)\"\n```\n\nAlso, before committing our first (real) version, let's update `package.json` with project name and version:\n```json\n{\n    \"name\": \"default-webapp-2024\",\n    \"version\": \"0.0.1\",\n    \"title\": \"Default Webapp 2024\",\n    ...\n}\n```\n\n\n### Build scripts\nWith our little proof-of-concept web page (soon to be a *web app*) in place and released,\nlet us define some basic scripts and commands underpinning our development process and deployment routine.\nFirst fetch some file handling tools:\n```shell\npnpm install rimraf --save-dev\npnpm install copyfiles --save-dev\n```\n\nUpdate `package.json` with a `\"scripts\"` block containing e.g., something like:\n```json\n{\n    ...\n    \"scripts\": {\n        \"clean\": \"pnpm rimraf --verbose dist\",\n        \"build\": \"pnpm build:prod\",\n        \"build:prod\": \"pnpm build:production\",\n        \"build:production\": \"pnpm clean \u0026\u0026 pnpm copyfiles index.html dist\",\n        \"deploy:staging\": \"pnpm build \u0026\u0026 pnpm vercel\",\n        \"deploy:prod\": \"pnpm deploy:production\",\n        \"deploy:production\": \"pnpm build:production \u0026\u0026 pnpm vercel --prod\"\n    },\n    ...\n}\n```\n\nThe declared scripts are listed with the command:\n```shell\npnpm run\n```\n\n### Deployment routine\nAfter the new version is completed in your local development environment:\n1. Deploy it to \"staging\" (a production-like environment):\n   ```shell\n   pnpm deploy:staging\n   ```\nWhen everything seems good to go:\n2. Update the \"version\" in `package.json`\n3. Commit to version control\n4. Deploy to production:\n   ```shell\n   pnpm deploy:production\n   ```\n\n**Notify your client that their web page (soon to be a *web app*) is up and running 🎉😄**\n...the very, very first version of it, that is.\n\nFor a real web app, a personal domain name is (close to) mandatory.\nWe won't do that as part of our setup, at least not yet.\nAnyway, the routines are described here:\nhttps://vercel.com/docs/projects/domains/\n\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is now on 121 MB – due to Vercel and file handling tools\u003cbr\u003e\n- Web page size (at rest): 30 B\u003cbr/\u003e\n- Web page size (network): 140 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.0.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/a0acd101b4a5132dedcb94a5c8088839e1d6b48d) | [deployment](https://defaultwebapp-rly4wlysd-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n\n## Set up technology stack\n\n### Vue\nLet us make this (static) web page **dynamic**, turning it into a \"*web app*\".\nThen we will manipulate the browser DOM, and the only way to do that is via the browser's ECMAScript-based DOM API.\n[Vue](https://vuejs.org) enables that, using a virtual DOM-based reactive template rendering.\n\nVue is a component-oriented web framework, revolving around *Vue components*, which is a quite flexible web component standard.\nA *Vue component* is, simply said, an ECMAScript function returning a render function.\nRender functions return virtual DOM (VDOM) nodes, possibly nested–which then become VDOM *trees*.\nThe virtual DOM is a programming concept where an ideal, or \"virtual\" representation of the UI, is kept in memory and synced with the \"real\" DOM. The concept was pioneered by React, and has been adopted in many other frameworks with different implementations, including Vue.\nVirtual DOM is more of a pattern than a specific technology, so there is no canonical implementation.\nWe can illustrate the idea using a simple example:\n```javascript\nconst vnode = {\n    type: 'div',\n    props: {\n        id: 'hello'\n    },\n    children: [\n        /* more vnodes */\n    ]\n}\n```\nHere, `vnode` is a plain ECMAScript object (a \"virtual node\") representing a `\u003cdiv\u003e` HTML element.\nIt contains all the information that we need to create the actual HTML/DOM element.\nIt also contains more children vnodes, which makes it the root of a VDOM tree.\nA runtime renderer can walk a virtual DOM tree and construct a real DOM tree from it.\nThis process is called *mounting*.\n\nSo, at the high level, this is what happens when a Vue component is mounted:\n1. **Compile:** Vue templates are compiled into render functions: functions that return VDOM trees.\n   This step can be done either ahead-of-time via a build step, or on-the-fly by using the runtime compiler.\n\n2. **Mount:** The *runtime renderer* invokes the render functions, walks the returned virtual DOM tree, and creates actual DOM nodes based on it.\n   This step is performed as a reactive effect, so it keeps track of all reactive dependencies that were used.\n\n3. **Patch:** When a dependency used during mount changes, the effect re-runs.\n   This time, a new, updated Virtual DOM tree is created. The runtime renderer walks the new tree, compares it with the old one, and applies necessary updates to the actual DOM.\n\nVue's `createApp` function takes in, almost whatever, and returns a VDOM node (ala `vnode` above), possibly a VDOM tree.\nIt is quite flexible in regard to the input parameter.\n\nIt may be a function.\nThen it is handled as the `render` function directly.\nIt may be an object.\nIf the object has just one function, it is handled as the `render` function.\nIf the object has more then one function, one of them must be named `render`.\nThe input object may also contain a _Vue template_, a declarative HTML-like representation of the web page.\n\n\n---\n\n\nWe are moving from plain text to [**H**yper**t**ext **M**arkup **L**anguage (HTML)](https://no.wikipedia.org/wiki/HTML/), ensuring a proper browser DOM element for \"hosting\" our Vue-based web app.\n\nUpdate `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value @\"\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    const render = function () {\n        return 'Default Webapp 2024 | Vue-Vite-Vercel (v0.1.0)'\n    };\n    Vue.createApp(render).mount('html');\n\u003c/script\u003e\n\"@\n```\n\n**unpkg** is a fast, global [content delivery network (CDN)](https://en.wikipedia.org/wiki/Content_delivery_network/) for everything managed by npm/pnpm.\nIt can be used for quick and easy file loading from any package using a URL like:\n```\nunpkg.com/:package@:version/:file\n```\n\n**NB!**\nWe are deliberately declaring the network protocol in our external URLs,\ninstead of using [protocol-relative URLs](https://en.wikipedia.org/wiki/URL#prurl).\nThis is for security reasons.\nThe use of protocol-relative URLs may accidentally utilize HTTP, and by that cause so-called *mixed content*\nwhich may make our web app vulnerable for [on-path attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 222 B\u003cbr/\u003e\n- Web page size (network): 171 KB (\u003ccode\u003evue.global.js\u003c/code\u003e) + 384 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\u003csub\u003e\nWe see that the size of our web app increases significantly with the integration of Vue.\nAlbeit, the startup time of our web app is not particularly affected...\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/93b1a3b7a2aea4009783403c33d9822d6196af19) | [deployment](https://defaultwebapp-itd9mwdfu-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nIt is more idiomatic and \"appropriate\" to create our own HTML element to put our Vue-based web content inside.\n\nUpdate `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value @\"\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    const render = function () {\n        return 'Default Webapp 2024 | Vue-Vite-Vercel (v0.1.1)'\n    };\n    Vue.createApp(render).mount('#app');\n\u003c/script\u003e\n\"@\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 222 B\u003cbr/\u003e\n- Web page size (network): 171 KB (\u003ccode\u003evue.global.js\u003c/code\u003e) + 352 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/6c89fce2f8c5943537e57a4727619b777ead25db) | [deployment](https://defaultwebapp-gd8mwo6dj-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nIt is maybe (well, yes, _maybe_) a bit more readable with a lambda-style anonymous render function.\n\nUpdate `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value @\"\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003eVue\n    .createApp(() =\u003e 'Default Webapp 2024 | Vue-Vite-Vercel (v0.1.2)')\n    .mount('#app')\n\u003c/script\u003e\n\"@\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 236 B\u003cbr/\u003e\n- Web page size (network): 171 KB (\u003ccode\u003evue.global.js\u003c/code\u003e) + 311 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/df7b80b403ca8cdbc2b00b734d15989569bd9de6) | [deployment](https://defaultwebapp-q66434uln-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nInstead of just returning plain text, let us return a VNODE.\nVue has a `h` function, which takes in application state, and return a VNODE.\n(\"h\" for **h**yperscript, meaning \"ECMAScript that produces HTML\").\nThis name is inherited from conventions shared by many virtual DOM implementations.\nA more descriptive name could be `createVNode()`,\nbut a shorter name helps when you have to call this function many times in a render function.\n\nUpdate `index.html`:\n```shell\nSet-Content -Path .\\index.html -Value @\"\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003eVue\n    .createApp(() =\u003e Vue.h(\n        'h1',\n        'Default Webapp 2024 | Vue-Vite-Vercel (v0.1.3)')\n    )\n    .mount('#app')\n\u003c/script\u003e\n\"@\n```\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 222 B\u003cbr/\u003e\n- Web page size (network): 171 KB (\u003ccode\u003evue.global.js\u003c/code\u003e) + 343 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.3\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/59e7cdbfabd168aad440dae66fde53462cf0ab02) | [deployment](https://defaultwebapp-5qgv8rh1v-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nOr, by providing an object (a data structure/shape),\nincluding state and a render function–for the Vue `createApp` function.\n\nReplace `index.html` with:\n```html\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"https://unpkg.com/vue@3/dist/vue.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003eVue\n    .createApp({\n        name: 'Default Webapp 2024',\n        data() {\n            return {\n                title: 'Default Webapp 2024',\n                version: '0.1.4',\n            }\n        },\n        render() {\n            return Vue.h(\n                'h1',\n                [\n                    `${this.title} | Vue-Vite-Vercel (v${this.version})`,\n                    []\n                ]\n            )\n        }\n    })\n    .mount('#app')\n\u003c/script\u003e\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 568 B\u003cbr/\u003e\n- Web page size (network): 171 KB (\u003ccode\u003evue.global.js\u003c/code\u003e) + 667 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.4\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/af90e6c368b12bcf13ba422cf648ef778cad3bf3) | [deployment](https://defaultwebapp-7w9rkmmee-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nVue is available in many distributions.\nHere we are just using the one that adds Vue as a global variable (`Vue`) within the DOM.\nAs we are writing/providing the render function ourselves, we are bypassing the somewhat expensive \"compile\" step, where Vue templates are compiled into render functions.\nThat means we can use the slightly slimmer Vue \"runtime\" distribution, where the compile step functions are omitted.\n\nExecute:\n```shell\n(Get-Content .\\index.html).Replace('vue.global.js', 'vue.runtime.global.js').Replace('0.1.4', '0.1.5') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 572 B\u003cbr/\u003e\n- Web page size (network): 121 KB (\u003ccode\u003evue.runtime.global.js\u003c/code\u003e) + 697 B\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.5\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/c608fe2ad2297c5515a539aa128785cbef0d99e6) | [deployment](https://defaultwebapp-5loe7jdxg-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nA \"production\" version is also available.\nHere the ECMAScript is minified and compacted, decreasing the download size even more.\n\nExecute:\n```shell\n(Get-Content .\\index.html).Replace('vue.runtime.global.js', 'vue.runtime.global.prod.js').Replace('0.1.5', '0.1.6') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 572 B\u003cbr/\u003e\n- Web page size (network): 48 KB (\u003ccode\u003evue.runtime.global.prod.js\u003c/code\u003e) + 700 B\u003cbr/\u003e\n- Web page response time: \u003c40 ms, a bit snappier\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.6\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/6a2d9f8229177f3d8e2be78aea04178e3622b61b) | [deployment](https://defaultwebapp-gi9ztx2jk-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### ECMAScript Modules\nECMAScript Modules (ESM) is (after many years in the working) available and supported directly by the browser.\nThe web app must be organized as a set of ESM modules, and typically hosted alongside the `index.html` file.\nIn addition, the web app will also fetch common third-party ESM components, like Vue, via the browser.\nThis capability has been supported by most modern browsers since approximately 2020.\n\nNevertheless, bundled web app artifacts are still the idiomatic way of deploying/hosting web apps –\nand we will also organize our wep app that way, in a bit.\nA (pre)bundled web app requires a build step.\n\nBut, let us first try out ESMthe browser, without a build step:\n\nReplace `index.html` with:\n```html\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript type=\"module\"\u003e\n    import {createApp, h} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'\n\n    createApp({\n        name: 'Default Webapp 2024',\n        data() {\n            return {\n                title: 'Default Webapp 2024',\n                version: '0.1.7',\n            }\n        },\n        render() {\n            return h(\n                'h1',\n                [\n                    `${this.title} | Vue-Vite-Vercel (v${this.version})`,\n                    []\n                ]\n            )\n        }\n    }).mount('#app')\n\u003c/script\u003e\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 592 B\u003cbr/\u003e\n- Web page size (network): 162 KB (\u003ccode\u003evue.esm-browser.js\u003c/code\u003e) + 705 B\u003cbr/\u003e\n- Web page response time: \u003c40 ms, feels a bit snappier)\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.7\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/a57e38c2f580a157d43fc1c6dc6616b3bd322ce7) | [deployment](https://defaultwebapp-31c0nx56r-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nThe browser ESM \"distro\" also has a \"runtime-only\" variant.\n\nExecute:\n```shell\n(Get-Content .\\index.html).Replace('vue.esm-browser.js', 'vue.runtime.esm-browser.js').Replace('0.1.7', '0.1.8') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 604 B\u003cbr/\u003e\n- Web page size (network): 117 KB (\u003ccode\u003evue.runtime.esm-browser.js\u003c/code\u003e) + 713 B\u003cbr/\u003e\n- Web page response time: \u003c40 ms, feels a bit snappier)\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.8\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/c6748eb32ffe3912bc20b0c60e99f1a1ba12cfb4) | [deployment](https://defaultwebapp-o1w4ev1q8-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\n...and a \"production\" variant; execute:\n```shell\n(Get-Content .\\index.html).Replace('vue.runtime.esm-browser.js', 'vue.runtime.esm-browser.prod.js').Replace('0.1.8', '0.1.9') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 609 B\u003cbr/\u003e\n- Web page size (network): 48.3 KB (\u003ccode\u003evue.runtime.esm-browser.prod.js\u003c/code\u003e) + 717 B\u003cbr/\u003e\n- Web page response time: \u003c40 ms, feels a bit snappier\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.9\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/a8cd136087c076d0a9e7783e151dc12a81320720) | [deployment](https://defaultwebapp-5cdn9ictn-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nExtending our simple placeholder page with some content;\na simple survey and status of the technology components we are planning to integrate into our web app.\n\nReplace the render function with:\n```javascript\nrender() {\n    return [\n        h(\n            'h1',\n            [\n                `${this.title}`,\n                []\n            ]\n        ),\n        h(\n            'h2', ['Technology stack status:',\n                []\n            ]\n        ),\n        h(\n            'ul',\n            ['',\n                [\n                    h('li', ['Git: ok']),\n                    h('li', ['GitHub: ok']),\n                    h('li', ['Vercel: ok']),\n                    h('li', ['Vue: (ok)']),\n                    h('li', ['Vite: -']),\n                    h('li', ['TypeScript: -']),\n                    h('li', ['Tailwind: -']),\n                    h('li', ['Vitest: -']),\n                    h('li', ['Pinia: -']),\n                    h('li', ['HTTP APIs: -']),\n                    h('li', ['TSC/ESLint: -'])\n                ]\n            ]\n        ),\n        h(\n            'p', [\n                `v${this.version}`,\n                []\n            ]\n        )\n    ]\n}\n```\nAnd execute:\n```shell\n(Get-Content .\\index.html).Replace('0.1.9', '0.1.10') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 1,6 KB\u003cbr/\u003e\n- Web page size (network): 47,4 KB (\u003ccode\u003evue.runtime.esm-browser.prod.js\u003c/code\u003e) + 564 B\u003cbr/\u003e\n- Web page response time: \u003c40 ms, feels a bit snappier\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.10\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/40d664de2e4deea1573da88ec39ce450fd627d84) | [deployment](https://defaultwebapp-m5ewqy68y-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nVue recommends using templates to build applications in the vast majority of cases.\nThere are situations where we need the full programmatic power of ECMAScript –\nthat's where we can use the render function.\n\nIt may seem tedious and cumbersome building the entire web app by functions,\nbut by using a functional programming style, declaring pure functions, and composing them – it is doable.\nIt will resemble [Elm](https://elm-lang.org)-style development.\n(One example is the [previous default webapp project](https://github.com/eirikt/default-webapp-elm-now), which used Elm.)\nOne of the effects will be increased testability.\nIt can be argued that the readability may be a bit lower than using declarative markup language templates.\n\nAnyway, we will choose the (seemingly) more idiomatic way of declarative markup language and Vue templates.\nVue templates use the \"mustache\" syntax (double curly braces).\nThis is Vue's basic form of data binding.\n\nReplace the render function block with:\n```vue\ntemplate: `\n    \u003ch1\u003e{{this.title}}\u003c/h1\u003e\n    \u003ch2\u003eTechnology stack status:\u003c/h2\u003e\n    \u003cul\u003e\n        \u003cli\u003eGit: ok\u003c/li\u003e\n        \u003cli\u003eGitHub: ok\u003c/li\u003e\n        \u003cli\u003eVercel: ok\u003c/li\u003e\n        \u003cli\u003eVue: (ok)\u003c/li\u003e\n        \u003cli\u003eVite: -\u003c/li\u003e\n        \u003cli\u003eTypeScript: -\u003c/li\u003e\n        \u003cli\u003eTailwind: -\u003c/li\u003e\n        \u003cli\u003eVitest: -\u003c/li\u003e\n        \u003cli\u003ePinia: -\u003c/li\u003e\n        \u003cli\u003eHTTP APIs: -\u003c/li\u003e\n        \u003cli\u003eTSC/ESLint: -\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003ev{{this.version}}\u003c/p\u003e\n`\n```\nNo, that won't work.\nThe \"runtime\" version we still are using does not support compiling Vue templates.\nRemove the \"runtime\" in 'https://unpkg.com/vue@3/dist/vue.runtime.esm-browser.prod.js', and bump the version to 0.1.11:\n```shell\n(Get-Content .\\index.html).Replace('vue.runtime.esm-browser.prod.js', 'vue.esm-browser.prod.js').Replace('0.1.10', '0.1.11') | Set-Content .\\index.html\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 1,0 KB\u003cbr/\u003e\n- Web page size (network): 75,1 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 1,1 KB\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.11\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/83a297900ff0443224fdaabc9b3786c3c3d9dab0) | [deployment](https://defaultwebapp-gz52pos5a-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### Complete `index.html` file with styling\nLet us establish a complete HTML file, and play around with some styling.\nIt is much easier to do that with the HTML-like template syntax we now are using.\nWe are also using the web-idiomatic UTF-8 character encoding;\nunlocking a vast array of multilingual letters, special characters, and \"emojis\".\n\nReplace `index.html` with:\n```html\n\u003c!DOCTYPE html\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003ctitle\u003eDefault Webapp 2024\u003c/title\u003e\n\u003cstyle\u003e\n    html {\n        font-family: \"Comic Sans MS\", sans-serif;\n    }\n\u003c/style\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript type=\"module\"\u003e\n    import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js'\n\n    createApp({\n        name: 'Default Webapp 2024',\n        data() {\n            return {\n                title: 'Default Webapp 2024',\n                version: '0.1.12',\n            }\n        },\n        template: `\n            \u003ch1\u003e{{this.title}}\u003c/h1\u003e\n            \u003ch3\u003eTechnology stack status:\u003c/h3\u003e\n            \u003cul\u003e\n                \u003cli\u003eGit: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eGitHub: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eVercel: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eVue: \u003cspan style=\"font-size: larger;font-weight: bolder;color: darkseagreen;\"\u003e(✓)\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eVite: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eTypeScript: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eTailwind: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eVitest: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003ePinia: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eHTTP APIs: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n                \u003cli\u003eTSC/ESLint: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003c/ul\u003e\n            \u003cp style=\"padding-top: 8px;font-family: monospace;color: grey;\"\u003ev{{this.version}}\u003c/p\u003e\n        `\n    }).mount('#app')\n\u003c/script\u003e\n```\nWe are using the Comic Sans font, just for the fun of it.\n(Sadly, it is mostly not available in mobile browsers...)\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 1,8 KB\u003cbr/\u003e\n- Web page size (network): 75,1 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 730 B\u003cbr/\u003e\n- Web page response time: \u003c60 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.12\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/e1433fda6c70553016361626ece4102d22fc7b62) | [deployment](https://defaultwebapp-knk2p8yrr-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\nBefore we move over to a \"bundled build\", let us investigate ESM in the browser some more.\n\nHere, the `index.html` file loads `main.js` as an ESM (`type=\"module\"`).\n`main.js` then loads Vue as an ESM, and also `App.js`, which contains our content.\n`App.js` is a _Vue component_, a `createApp`-compliant object with a _Vue template_.\nInternally, Vue's `createApp` function will return a VDOM node, possibly a VDOM tree, which Vue uses when updating the DOM.\n\nAll initial application state can now be moved from `index.html` to `App.js`.\n\n`index.html` will now look like this:\n```html\n\u003c!DOCTYPE html\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003ctitle\u003eDefault Webapp 2024\u003c/title\u003e\n\u003cstyle\u003e\n    html {\n        font-family: sans-serif;\n    }\n\u003c/style\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"./main.js\" type=\"module\"\u003e\u003c/script\u003e\n```\n...\n\n`main.js`:\n```shell\nNew-Item main.js\n```\n```javascript\nimport {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js';\nimport App from './App.js';\n\ncreateApp(App).mount('#app');\n```\n...\n\n`App.js`:\n```shell\nNew-Item App.js\n```\n```javascript\nexport default {\n    name: 'Default Webapp 2024',\n    data() {\n        return {\n            title: 'Default Webapp 2024',\n            version: '0.1.13',\n        }\n    },\n    template: `\n        \u003ch1\u003e{{this.title}}\u003c/h1\u003e\n        \u003ch3\u003eTechnology stack status:\u003c/h3\u003e\n        \u003cul style=\"list-style-type: none;\"\u003e\n            \u003cli\u003eGit: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel\"\u003eGitHub\u003c/a\u003e: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eVercel: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e✓\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eVue: \u003cspan style=\"font-size: larger;font-weight: bolder;color: darkseagreen;\"\u003e(✓)\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eVite: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eTypeScript: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eTailwind: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eVitest: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003ePinia: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eHTTP APIs: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n            \u003cli\u003eTSC/ESLint: \u003cspan style=\"font-size: larger;color: red;\"\u003e✗\u003c/span\u003e\u003c/li\u003e\n        \u003c/ul\u003e\n        \u003cp style=\"padding-top: 8px;font-family: monospace;color: grey;\"\u003e[ v{{this.version}} | \u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel/blob/main/README.md\" style=\"font-family: monospace;color: grey;\" target=\"_blank\"\u003eDocumentation\u003c/a\u003e ]\u003c/p\u003e\n    `\n};\n```\n\n...\n\n\nWe need to deploy all three files that comprise our wep app, so we must update our deployment script:\n```shell\n(Get-Content .\\package.json).Replace('index.html ', 'index.html main.js App.js ').Replace('0.1.12', '0.1.13') | Set-Content .\\package.json\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 2,0 KB\u003cbr/\u003e\n- Web page size (network): 75,1 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 537 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 314 B (\u003ccode\u003emain.js\u003c/code\u003e) + 613 B (\u003ccode\u003eApp.js\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.13\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/d21882f6b8b852d70940ac169cc37ba82dc4091d) | [deployment](https://defaultwebapp-rih2176g4-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n**NB!**\nUntil noe we could have opened the webapp locally by just opening the `index.html` file in a browser locally.\nNot anymore.\nIt will be effectively stopped by default [CORS}(https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) behavior.\nE.g.:\n```\nAccess to script at 'file:///C:/Users/User/Projects/defaultwebapp/dist/main.js' from origin 'null' has been blocked by CORS policy:\nCross origin requests are only supported for protocol schemes: chrome-extension, chrome-untrusted, data, edge, http, https, isolated-app.\n```\nIt is possible to circumvent this by opening the browser with a flag,\nor maybe with [data URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data) and `modulepreload`,\nbut it is not really recommended.\nOur workaround is a simple deployment routine.\n\n...\n\n\nWith ESM, it is possible to preload modules.\nThis for preventing startup delay, caused by the browser-default deferred downloading of modules.\n\nReplace `index.html` with:\n```html\n\u003c!DOCTYPE html\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003ctitle\u003eDefault Webapp 2024\u003c/title\u003e\n\u003cstyle\u003e\n    html {\n        font-family: sans-serif;\n    }\n\u003c/style\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n\u003cscript src=\"./main.js\" type=\"module\"\u003e\u003c/script\u003e\n\u003c!--\n  To prevent waterfall-loading, we preload all the ESM files of our application.\n--\u003e\n\u003clink rel=\"modulepreload\" href=\"https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js\"\u003e\n\u003clink rel=\"modulepreload\" href=\"./App.js\"\u003e\n```\n(Closing tags for `\u003cmeta\u003e` and `\u003clink\u003e` are not required.)\n\nBump the version:\n```shell\n(Get-Content .\\App.js).Replace('0.1.13', '0.1.14') | Set-Content .\\App.js\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is still on 121 MB\u003cbr/\u003e\n- Web page size (at rest): 2,0 KB\u003cbr/\u003e\n- Web page size (network): 75,1 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 537 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 314 B (\u003ccode\u003emain.js\u003c/code\u003e) + 613 B (\u003ccode\u003eApp.js\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c40 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.14\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/f7a8aac85f849d4bde0e1b43653343d919928137) | [deployment](https://defaultwebapp-656rxkdbe-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### Viewport \u003cmeta\u003e tag\nOne final thing before we move over to the \"bundled build\".\nThe \"viewport\" `\u003cmeta\u003e` tag is used to control the viewport's size and shape.\nThe browser's viewport is the area of the window in which web content can be seen.\nThis may not be the same size as the rendered page.\n\nSome mobile devices and other narrow screens render pages in a virtual window or viewport,\nwhich is usually wider than the screen, and then shrink the rendered result down so it can all be seen at once.\nUsers can then zoom and pan to look more closely at different areas of the page.\nThis virtual viewport is a way to make non-mobile-optimized sites in general look better on narrow screen devices.\nHowever, this mechanism is not so good for pages that are optimized for narrow screens using media queries.\nThe viewport \u003cmeta\u003e element mitigates this problem of virtual viewport on narrow screen devices.\n\nA typical mobile-optimized site contains something like the following:\n```html\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n```\n\nBump the version:\n```shell\n(Get-Content .\\App.js).Replace('0.1.14', '0.1.15') | Set-Content .\\App.js\n```\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.1.15\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/b02bec76c64ed9b300553638316a299ba0baf658) | [deployment](https://defaultwebapp-cy9x4wa7g-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Vite\n[Vite](https://vitejs.dev) is a new take on the building of ECMAScript-based project.\n(For my part, I am happy to leave **webpack** behind...)\nVite is a great alternative; it almost feels like there is no build step at all.\nFurthermore, you get incredible fast automatic (\"hot\") module reloading (HMR), which you can only get with sophisticated tooling.\nVite uses [esbuild](https://esbuild.github.io) (bundler) and [Rollup](https://rollupjs.org) (plugins) under the hood.\n\nInstall Vite:\n```shell\npnpm install vite --save-dev\n```\n\n```shell\npnpm vite --version\n```\n\n\n#### Vite initial config\nVite is all about convention over configuration.\nEven so, Vite is highly configurable.\n\n```shell\nNew-Item vite.config.js\n```\n\nAdd initial `vite.config.js`:\n```javascript\nimport {defineConfig} from 'vite';\n\nexport default defineConfig({\n    build: {\n        minify: false,\n        terserOptions: {\n            compress: false,\n            mangle: false,\n        },\n        emptyOutDir: false\n    },\n    server: {\n        port: 9090,\n        strictPort: true\n    }\n});\n```\nFor now, we're changing the development environment port.\nAlso, we are (temporarily) disabling Vite's minification process to have a look at the built artifacts.\n\nHave a look at the complete Vite configuration options here:\nhttps://vite.dev/config/\n\n\n#### Internal development web server\nVite has an internal development web server, supporting HMR for an incredible fast developer feedback cycle.\nUpdate `package.json` with a new script for the Vite \"dev\" web server:\n```\n\"dev\": \"pnpm vite dev\",\n```\n\nOpen a new terminal window:\n```shell\npnpm dev\n```\nThis will spin up a Vite \"dev\" web server.\nNavigate to the given dev URL, http://localhost:9090,\nor just press `o + ENTER` which will open a browser window with your web app locally running.\n\n\n#### Completing the deployment routine\nVite has two kinds of builds.\nThe default is *production*, but Vite also has the notion of a *staging* build.\nWe will extend our release routine wita \"staging\" step.\n\nIn `package.json`, update the \"build\" step, and add new \"build:staging\" and \"staging\" steps:\n```\n\"build\": \"pnpm build:staging\",\n\"build:staging\": \"pnpm clean \u0026\u0026 pnpm vite build --mode staging\",\n\"staging\": \"pnpm build:staging \u0026\u0026 pnpm vite preview --open\",\n```\nThe \"staging\" step will be like \"dev\" step, but with a built and bundled web app.\n\nIn `package.json`, update the \"build:production\" and \"deploy:staging\":\n```\n\"build:production\": \"pnpm clean \u0026\u0026 pnpm vite build\",\n\"deploy:staging\": \"pnpm build:staging \u0026\u0026 pnpm vercel\",\n```\n\nThis means we can remove the \"copyfiles\" component dependency:\n```shell\npnpm remove copyfiles\n```\n\nHave a look at the `pnpm-lock.yaml` file.\nHow is the addition of Vite and the removal of \"copyfiles\" reflected?\n\n...\n\n\nRun:\n```shell\npnpm build\n```\nHave a look at the generated content in the `/dist/assets/` folder.\n\nThe `index.html` file looks quite similar.\nThe only difference is the reference to the generated `/dist/assets/*.js` file,\nand a [Base64](https://en.wikipedia.org/wiki/Base64) version of `App.js`.\nDo notice the [crossorigin](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) parameter added to the `\u003cscript\u003e` tag.\n\nHave a look at the built bundled web app before it is too late.\nIt will be rather unreadable in due course –\nwhen we embed Vue into the built artifacts.\n\n\n### Deployment routine (v2)\nSo, our new deployment routine is now:\n1. Complete the new version using Vite's HMR functionality in your local development environment:\n   ```shell\n   pnpm dev\n   ```\n   Do test the build and built artifacts before deployment (\"preview\"):\n   ```shell\n   pnpm staging\n   ```\n2. When complete, deploy it to \"staging\" (a production-like environment):\n   ```shell\n   pnpm deploy:staging\n   ```\nWhen everything seems good to go:\n3. Update the \"version\" in `package.json`\n4. Commit to version control\n5. Deploy to production:\n   ```shell\n   pnpm deploy:production\n   ```\n6. Test the new version in various browsers\n\n\n---\n\n\nReturning to our new version, mark Vite as \"integrated\" in our tech stack status.\nAnd then bump the version:\n```shell\n(Get-Content .\\App.js).Replace('0.1.15', '0.2.0') | Set-Content .\\App.js\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 140 MB\u003cbr/\u003e\n- Web page size (at rest): 2,9 KB (\u003ccode\u003eindex.html\u003c/code\u003e) + 3,0 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 74,4 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 1,2 KB (bundle) + 1,5 KB (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.2.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/dc792e68f0c8b651cb3349c135cef2af80f98b2f) | [deployment](https://defaultwebapp-lbioctv2z-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Project workspace folder structure\nWe have many more components to add to our web stack; a lot more files to add.\nOur project workspace folder will very soon be getting rather intractable.\nSo, let us prepare for that.\n\nThis is a somewhat idiomatic folder structure for Vue-based web projects:\n```\n/dist\n  ...\n/public\n  ...\n/src\n/src/assets\n/src/scripts\n/src/styles\n/src/vue/components\n/src/vue/directives\n```\n\nExecute:\n```shell\nNew-Item public -Type Directory\nNew-Item src -Type Directory\ncd .\\src\\\nNew-Item assets -Type Directory\nNew-Item scripts -Type Directory\nNew-Item styles -Type Directory\nNew-Item vue -Type Directory\ncd .\\vue\\\nNew-Item components -Type Directory\nNew-Item directives -Type Directory\ncd ..\ncd ..\n```\n\nMove `main.js`:\n```shell\ngit mv .\\main.js .\\src\\scripts\\main.js\n```\n\nMove `App.js`:\n```shell\ngit mv .\\App.js .\\src\\vue\n```\n\n*NB!* Leave the `index.html` in the project root folder.\nIt seems most happy living on the same level as `vite.config.js`.\n\nUpdate file paths!\n\nBump the version:\n```shell\n(Get-Content .\\src\\vue\\App.js).Replace('0.2.0', '0.2.1') | Set-Content .\\src\\vue\\App.js\n```\nIt is cumbersome and error-prone, having the version id several places...\nWe must mitigate that, later on.\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 140 MB\u003cbr/\u003e\n- Web page size (at rest): 2,9 KB (\u003ccode\u003eindex.html\u003c/code\u003e) + 3,0 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 74,4 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 1,2 KB (bundle) + 1,5 KB (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c50 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.2.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/aaca2ac02e417b357b1c99568e4c5e88c2755aad) | [deployment](https://defaultwebapp-rf8akdjwe-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### TypeScript\n[TypeScript](https://www.typescriptlang.org) is a typed version of JavaScript/ECMAScript.\nTypeScript transpiles to ECMAScript,\nlike a [lot of other web programming languages](https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS).\nTypeScript has gained a lot of traction and is by many regarded as the default web programming language.\n\nRename `vite.config.js`:\n```shell\ngit mv .\\vite.config.js .\\vite.config.ts\n```\n\nRewrite Vite configuration content:\n```typescript\nimport type {UserConfig} from 'vite'\n\nexport default {\n    build: {\n        minify: false,\n        terserOptions: {\n            compress: false,\n            mangle: false,\n        },\n        emptyOutDir: false\n    },\n    server: {\n        port: 9090,\n        strictPort: true\n    }\n} satisfies UserConfig\n```\n\nRename `main.js`:\n```shell\ngit mv .\\src\\scripts\\main.js .\\src\\scripts\\main.ts\n```\n\nRename `App.js`:\n```shell\ngit mv .\\src\\vue\\App.js .\\src\\vue\\App.ts\n```\n\nMark TypeScript as \"integrated\" in our tech stack status.\n\nUpdate file paths!\n\nOptionally, do minor re-writes of the two files.\n\nBump the version:\n```shell\n(Get-Content .\\src\\vue\\App.ts).Replace('0.2.1', '0.2.2') | Set-Content .\\src\\vue\\App.ts\n```\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 140 MB\u003cbr/\u003e\n- Web page size (at rest): 2,9 KB (\u003ccode\u003eindex.html\u003c/code\u003e) + 3,2 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 74,4 KB (\u003ccode\u003evue.esm-browser.prod.js\u003c/code\u003e) + 1,2 KB (bundle) + 1,5 KB (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c60 ms\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.2.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/fb7716fcadbde05c41746be69c185f935a862b09) | [deployment](https://defaultwebapp-lzdm3ynud-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Vue via Vite plugin (instead of via CDN)\nLet us source Vue via Vite, so it will as a regular project dependency\n(together with all the others in `package.json`).\n\nThis will be as using Vue as an ESM module.\nIt will be bundled together with our application and presentation logic by Vite.\n\nInstall Vue and then Vite's Vue plugin:\n```shell\npnpm install vue --save-dev\npnpm install @vitejs/plugin-vue --save-dev\n```\n\nUpdate `vite.config.ts`, import Vue plugin:\n```typescript\nimport vue from '@vitejs/plugin-vue'\n```\n\nAnd add Vue as a Vite build plugin:\n```code\n...\nplugins: [\n    vue()\n],\n...\n```\nNow, source Vue internally instead of from CDN; in `main.ts`:\n```typescript\nimport {createApp} from 'vue/dist/vue.esm-bundler'\n```\nYet another Vue distribution variant...\nThis one is custom-made for bundlers, like Vite.\nIt is ESM-based, and only fetching Vue features needed for this particular build.\n\nAlso, remove all `\u003clink rel=\"modulepreload\" ...\u003e` from `index.html`\nUpdate version in `package.json` and `App.ts` to 0.2.3 (still a cumbersome and error-prone routine...).\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 157 MB\u003cbr/\u003e\n- Web page size (at rest): 315 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 438 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 111 KB (bundle) + 426 B (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: 60-150 ms, considerably more latency...\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.2.3\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/290356e5cda5433f0c7046635bacd1203bbe7aae) | [deployment](https://defaultwebapp-o7n6r9kxl-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\nHave another look at the generated `.js` file in the `/dist/assets/` folder.\nIt has grown significantly, as Vue is embedded inside.\n\nAll kinds of minification routines are still deactivated in Vite.\n\n\n---\n\n\n### Vue components\nVue is a component-oriented web application framework.\nThis Vue component will contain the initial state,\nand the initial visual components, which will be added/bound to the DOM by Vue.\n\nLet us rewrite our `App.ts` Vue component to a [Vue Single-File Component (SFC)](https://vuejs.org/guide/scaling-up/sfc/).\nSFC is a special file format that allows us to encapsulate the logic, template, and styling of a Vue component in a single file.\nSFC files have a `.vue` suffix.\n\nLet us promote our Vue component `App.ts` to our first (and main) Vue SFC.\n\nRename `App.ts`:\n```shell\ngit mv .\\src\\vue\\App.ts .\\src\\vue\\App.vue\n```\n\nReplace our \"ESM-typescripted\" content with:\n```vue\n\u003cscript setup lang='ts'\u003e\n    const title = 'Default Webapp 2024'\n    const version = '0.3.0'\n\n    const notIntegrated = '✗'\n    const integrated = '✓'\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n    \u003ch1\u003e{{ title }}\u003c/h1\u003e\n    \u003cp style=\"font-weight: bold;\"\u003eTechnology stack status:\u003c/p\u003e\n    \u003cul style=\"list-style-type: none;\"\u003e\n        \u003cli\u003eGit: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel\" target=\"_blank\"\u003eGitHub\u003c/a\u003e: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eGitHub: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eVercel: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eVue: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eVite: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eTypeScript: \u003cspan style=\"font-size: larger;font-weight: bolder;color: green;\"\u003e{{ integrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eTailwind: \u003cspan style=\"font-size: larger;color: red;\"\u003e{{ notIntegrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eVitest: \u003cspan style=\"font-size: larger;color: red;\"\u003e{{ notIntegrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003ePinia: \u003cspan style=\"font-size: larger;color: red;\"\u003e{{ notIntegrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eHTTP APIs: \u003cspan style=\"font-size: larger;color: red;\"\u003e{{ notIntegrated }}\u003c/span\u003e\u003c/li\u003e\n        \u003cli\u003eTSC/ESLint: \u003cspan style=\"font-size: larger;color: red;\"\u003e{{ notIntegrated }}\u003c/span\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp style=\"padding-top: 8px;font-family: monospace;color: grey;\"\u003e\n        [\n        v{{ version }} |\n        \u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel/blob/main/README.md\"\n           style=\"font-family: monospace;color: grey;\" target=\"_blank\"\u003eDocumentation\u003c/a\u003e\n        ]\n    \u003c/p\u003e\n\u003c/template\u003e\n```\nOpen a diff representation of the new content end the previous one.\nCompare them.\n\nAlter the imports in the top of the `main.ts` file:\n```typescript\nimport {createApp} from 'vue'\nimport App from '../vue/App.vue'\n...\n```\nAs the web app startup now goes through a Vue component,\nwe can now reference Vue via the alias \"vue\", declared in the embedded Vue source.\n\nUpdate version in `package.json` and `App.vue` to 0.3.0 (still a cumbersome and error-prone routine...).\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 157 MB\u003cbr/\u003e\n- Web page size (at rest): 315 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 175 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 45 KB (bundle) + 469 B (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c40 ms, snappy again\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.3.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/d0a576ab092e0189f886b9ffe6fd4aa8119487cc) | [deployment](https://defaultwebapp-3n16wx3fz-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### Project config state in Vue from 'package.json'\nUpdate `App.vue` \"script\" part with:\n```vue\n...\n\u003cscript setup lang='ts'\u003e\n    import packageJson from '../../package.json'\n\n    const title = packageJson.title\n    const version = packageJson.version\n    ...\n```\n\nUpdate \"version\" in `package.json` (only) to 0.3.1.\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 157 MB\u003cbr/\u003e\n- Web page size (at rest): 315 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 174 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 45 KB (bundle) + 428 B (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: 60-150 ms, considerably more latency...\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.3.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/0180c36e5b4711063a41bf53264fbbbc2fd1b237) | [deployment](https://defaultwebapp-5ivbl2dlt-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n#### Activating Vite's (default) minification routines\nIn `vite.config.ts`, remove the entire `build` object (for now).\nLet us use Vite's default build routine.\n\nExecute the deployment routine, described above.\n\u003cp\u003e\n\u003csub\u003e\n- The project workspace folder is 157 MB\u003cbr/\u003e\n- Web page size (at rest): 315 B (\u003ccode\u003eindex.html\u003c/code\u003e) + 61 KB (bundle)\u003cbr/\u003e\n- Web page size (network): 25 KB (bundle) + 428 B (\u003ccode\u003eindex.html\u003c/code\u003e)\u003cbr/\u003e\n- Web page response time: \u003c40 ms, snappy again\u003cbr/\u003e\n\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.3.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/4b71b70bac641baa89f247713fb2833325538a27) | [deployment](https://defaultwebapp-2jxsoftru-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\n### Tailwind\nTraditionally, whenever you need to style something on the web, you write Cascading Style Sheets (CSS) code.\nCSS is a (mostly) declarative domain-specific programming language.\nCSS tend to end up in separate files with the `.css` suffix.\n\nTailwind is yet another attempt to standardize CSS utility class names in such a way that it actually works for all.\nWith Tailwind, you style elements by applying pre-existing classes *directly in your HTML*.\n\nThe main motivation is thoroughly argued for here:\nhttps://adamwathan.me/css-utility-classes-and-separation-of-concerns/\n\n...\n\n\nBut before we start styling using Tailwind, let us see how our web app looks like without any styling;\nor more correctly, with the browser's default styling.\n\nRemove `\u003cstyle\u003e` tag from `index.html`, and remove all styling in `App.vue`.\n\nUpdate version in `package.json` to 0.4.0.\n\nExecute the deployment routine, described above.\n\nHave a look at the web app, in different browsers.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/89544a487e696937872a1e49aaa421a6f12b7c1a) | [deployment](https://defaultwebapp-nps8qb62j-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\nInstall Tailwind (`tailwindcss`) and its peer dependencies:\n```shell\npnpm install tailwindcss --save-dev\npnpm install postcss --save-dev\npnpm install autoprefixer --save-dev\n```\n\nTo load an ECMAScript Modules (ESM) as the default, (directly via npm),\nset `\"type\": \"module\"` in the `package.json`.\n(Another alternative is to use the `.mjs` file extension.)\n\nTailwind is integrated into the Vite build process via [PostCSS](https://postcss.org);\na tool for transforming CSS using ECMAScript.\n\nCreate initial PostCSS and Tailwind config files:\n```shell\npnpm tailwindcss init --ts --postcss\n```\n(It seems that `postcss.config.js` is available as `.js` only.)\n\nAdd content file path in the generated `tailwind.config.ts` file:\n```typescript\nimport type {Config} from 'tailwindcss'\n\nexport default {\n    content: ['./src/vue/App.vue'],\n    //plugins: [],\n    //theme: {extend: {}}\n} satisfies Config\n```\n...which tell Tailwind to scan our `App.vue` when creating our web app's styling.\n(Configuration settings we don't yet need are commented out.)\n\nCreate initial application stylesheet `main.css` file:\n```shell\nNew-Item ./src/styles/main.css\n```\n```css\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n```\nTailwind consists of three \"layers\":\n1. \"**base**\"\n\n    Base styles for specific HTML elements\n\n2. \"**components**\"\n\n    More complicated classes you want to add to your project that you’d still like to be able to override with utility classes.\n    Traditionally these would be classes like card, btn, badge — that kind of thing.\n    The components layer is also a good place to put custom styles for any third-party components you’re using.\n\n3. \"**utilities**\"\n\n    Your own custom utility classes\n\nFinally, import the styling in `main.ts`:\n```typescript\nimport '../styles/main.css'\n```\n\nI guess we now can mark Tailwind as \"integrated\", in `App.vue`:\n```typescript\nconst tailwind = integrated\n```\n\n...\n\n\nNow, the web app is styled with Tailwind's default theme, [Preflight](https://tailwindcss.com/docs/preflight).\nIt is an opinionated set of base styles for Tailwind projects, built on top of [modern-normalize](https://github.com/sindresorhus/modern-normalize),\n\nThe default Tailwind theme should be suitable as a starting point, even we have lost our heading style.\nThe header looks quite ...modest, to say the least.\nThe reason is that in the default Tailwind styling, headings are unstyled, and have the same font-size and font-weight as normal text.\nThe reason for this is two-fold the Tailwind project argues:\n- It helps you avoid accidentally deviating from your type scale.\n  By default, browsers assign sizes to headings that don’t exist in Tailwind’s default type scale,\n  and aren’t guaranteed to exist in your own type scale.\n- In UI development, headings should often be visually de-emphasized. Making headings unstyled by default means any styling you apply to headings happens consciously and deliberately.\n  (See: https://tailwindcss.com/docs/preflight#headings-are-unstyled)\n\nThe same things are true for other important elements, like anchors.\nSo, it is correct to say that we are ready to employ Tailwind styling.\nOur web app's styling is rebooted and reset!\n\n...\n\n\nExecute the deployment routine, described above.\n\nHave a look at the generated `.js` and `.css` files in the `/dist/assets/` folder.\nOpen it in an IDE, and format it to make it human-readable.\nThe CSS file is Tailwind's default \"reset\" theming as mentioned above;\nsmoothing over cross-browser inconsistencies and making it easier for you to work within the constraints of your design system.\n\nHave a look at the web app, in different browsers.\nNow, they should look rather uniform, compared to the default browser styling.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/dcb68f4f37bcc9865a2dbdd1032b43b3fd5b707c) | [deployment](https://defaultwebapp-osm9ydahc-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\nOk, let us add some (default) styles, in `main.css`:\n```css\n...\n@layer base {\n    html {\n        @apply text-gray-600\n    }\n\n    h1 {\n        @apply text-3xl font-semibold\n    }\n\n    h2 {\n        @apply text-lg font-semibold\n    }\n\n    a {\n        @apply underline\n    }\n\n    li \u003e a {\n        @apply no-underline\n    }\n}\n@layer components {}\n@layer utilities {}\n```\nHere we are modifying Tailwind's \"base\" layer:\n1. Setting the font color to a dark gray\n2. Modifying `h1` and `h2` elements\n3. Modifying `a` element (underline marker only, and no underline in lists)\n\nThese will apply for all HTML elements in this Vue instance; our entire web app.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/594575964df5404d054ac44c02cf2c10f5cc6464) | [deployment](https://defaultwebapp-7bt5oi1e4-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\nAs an example, let us use Tailwind's [recommended font](https://rsms.me/inter/).\nUsing a custom font is nice because it allows us to make the components look the same on all browsers and operating systems.\n\nThe easiest way is to first add it via the CDN, in `index.html`:\n```html\n\u003clink rel=\"preconnect\" href=\"https://rsms.me\"\u003e\n\u003clink rel=\"stylesheet\" href=\"https://rsms.me/inter/inter.css\"\u003e\n```\n\nThe `preconnect` keyword for the `rel` attribute of the `\u003clink\u003e` element is a hint to browsers that the user is likely to need resources from the target resource's origin.\nTherefore the browser can likely improve the user experience by preemptively initiating a connection to that origin.\n\"Preconnecting\" speeds up future loads from a given origin by preemptively performing part or all of the handshake (DNS+TCP for HTTP, and DNS+TCP+TLS for HTTPS origins).\n`\u003clink rel=\"preconnect\"\u003e` will provide a benefit to any future cross-origin HTTP request, navigation or subresource.\nIt has no benefit on same-origin requests because the connection is already open.\n\nIf a page needs to make connections to many third-party domains, \"preconnecting\" them all can be counterproductive.\nThe `\u003clink rel=\"preconnect\"\u003e` hint is best used for only the most critical connections.\nFor the others, just use `\u003clink rel=\"dns-prefetch\"\u003e` to save time on the first step—the DNS lookup.\n\nAdd \"Inter var\" to your \"sans\" font family in your Tailwind configuration.\nUpdate `tailwind.config.ts` with:\n```typescript\nimport defaultTheme from 'tailwindcss/defaultTheme'\n```\nand:\n```typescript\ntheme: {\n  extend: {\n    fontFamily: {\n      sans: ['Inter var', ...defaultTheme.fontFamily.sans]\n    }\n  }\n}\n```\nHere we are extending the `fontFamily.sans` configuration in the Tailwind theme (which is the default theme).\nWe are simply putting \"Inter var\" as the prioritized font,\nwith the rest of Tailwind's default theme's `fontFamily.sans` configuration as secondary alternatives.\nIt is added as a [destructured assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).\n\nExecute the deployment routine, described above.\n\nWe see that the network load of our web app increases significantly with the introduction of the external font.\nAlbeit, the startup time of our web app is still not particularly affected.\nThe `preconnect` hint helps out here.\nAlso, browser-external fonts should be cached in the browser.\nMore on that later on.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.3\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/4e79f1765cc603d733ea1c3109ced8845da1f6d8) | [deployment](https://defaultwebapp-hrd86lqz1-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\nAdd simple page layout to `App.vue`, using \"semantic\" HTML elements:\n```vue\n\u003ctemplate\u003e\n    \u003carticle\u003e\n        \u003cheader\u003e\n            \u003ch1\u003e{{ title }}\u003c/h1\u003e\n        \u003c/header\u003e\n        \u003chr/\u003e\n        \u003csection\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003eTechnology stack status\u003c/h2\u003e\n                \u003cul\u003e\n                    \u003cli class=\"checked\"\u003eGit\u003c/li\u003e\n                    \u003cli\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel\" target=\"_blank\"\u003eGitHub\u003c/a\u003e\u003cspan class=\"{{ checked }}\"\u003e\u003c/span\u003e\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVercel\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVue\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVite\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eTypeScript\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eTailwind\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eVitest\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003ePinia\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eHTTP APIs\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eTSC/ESLint\u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/div\u003e\n        \u003c/section\u003e\n        \u003chr/\u003e\n        \u003cfooter\u003e\n            \u003cspan\u003e\n                \u003cspan class=\"code\"\u003ev{{ version }}\u003c/span\u003e\n                \u003cspan class=\"text-gray-400\"\u003e | \u003c/span\u003e\n                \u003cspan class=\"text-sm text-gray-400\"\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel/blob/main/README.md\" target=\"_blank\"\u003eDocumentation\u003c/a\u003e\u003c/span\u003e\n            \u003c/span\u003e\n            \u003cspan class=\"code text-gray-400\"\u003ebuilt: {{ buildTimestamp }}\u003c/span\u003e\n        \u003c/footer\u003e\n    \u003c/article\u003e\n\u003c/template\u003e\n```\n\nAlso, update script block, in `App.vue`:\n```vue\n\u003cscript setup lang='ts'\u003e\n    import packageJson from '../../package.json'\n\n    const title = packageJson.title\n    const version = packageJson.version\n    const buildTimestamp = new Date()\n\u003c/script\u003e\n```\n\nThis does not look particularly good.\nIt's all cramped together... we want to use the entire screen.\nWe need som styling.\n\nTo fix the page layout, we can use Tailwind's Flexbox classes.\nTailwind's Flexbox classes are more or less a one-to-one mapping of standard CSS Flexbox directives.\n\nThe `article` element represents an entire page in this application.\n```html\n\u003carticle class=\"flex flex-col justify-between h-dvh\"\u003e\n```\nThe Flexbox direction class `flex-col` aligns the flexbox items vertically.\n\nThe `h-*` Tailwind classes are utilities for setting the height of an element.\nThe `h-screen` class is equivalent to `height: 100vh;`.\nThe `h-svh` class is equivalent to `height: 100svh;`.\n\nThe `h-dvh` class is equivalent to `height: 100dvh;`\nIt will use the current viewport height, excluding user agent’s interface.\nSee: https://tennant.io/css-viewport-units-what-they-mean-vh-dvh-svh-etc/\n\nThe `section` HTML element represents a horizontal full-width box in this application.\n```html\n\u003csection class=\"basis-full\"\u003e\n```\nThe `basis-*` Tailwind classes are utilities for controlling the initial size of flex items.\nThe `basis-full` class is equivalent to `flex-basis: 100%;`.\n\nAlso, add Flexbox to the footer.\nFlexbox declarations may be arbitrarily nested:\n```html\n\u003cfooter class=\"flex flex-row justify-between\"\u003e\n```\n(The Flexbox direction class `flex-row` is the default and may be omitted.)\n\n...\n\n\nWe were using some extra CSS classes above.\nAdd them to `main.css`, as Tailwind \"utilities\" layer class extensions:\n```css\n...\n@layer utilities {\n    .capitalized::first-letter {\n        text-transform: capitalize;\n    }\n\n    .unchecked {\n        @apply after:content-['✗'] after:content-center after:font-bold after:text-xl after:text-red-600 after:ml-2\n    }\n\n    .checked {\n        @apply after:content-['✓'] after:content-center after:font-bold after:text-xl after:text-green-600 after:ml-2\n    }\n\n    .code {\n        @apply font-mono text-sm text-gray-400\n    }\n\n    .warning {\n        @apply text-sm text-amber-500\n    }\n\n    .todo {\n        @apply font-mono text-sm text-red-500\n    }\n\n    /* DEBUG */\n    article, section, div, span, ul, li \u003e * {\n        /* Activate when needed*/\n        border: 1px dotted red\n    }\n}\n```\nSome of the state is also moved from `App.vue` to the `checked`/`unchecked` classes.\n\nAlso, some handy \"debug\" style classes are added.\nThey are activated.\nDeactivate/comment them out at will.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.4\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/027c5ae3989fcb174c10fa2ae92516d71f192ba0) | (missing deployment) ]\u003c/sub\u003e\n\n...\n\n\nFinally, add margins and padding to the web page.\nIn general, don't be afraid to add some decent margins.\nHere we can play around with Tailwind's excellent support for responsive design, see https://tailwindcss.com/docs/responsive-design/.\n\n**NB!**\n*Use padding instead of margins for elements that are adjacent to the viewport's edges*.\nAccording to the [box model](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model/) margins operates outside the HTML element.\nBecause of this it may push the element outside the visible screen and cause scrolling bars to appear.\n\n```vue\n\u003ctemplate\u003e\n    \u003carticle class=\"flex flex-col justify-between h-dvh p-6\"\u003e\n        \u003cheader class=\"mt-4\"\u003e\n            \u003ch1\u003e{{ title }}\u003c/h1\u003e\n        \u003c/header\u003e\n\n        \u003chr class=\"my-2\"/\u003e\n\n        \u003csection class=\"basis-full my-4\"\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003eTechnology stack status\u003c/h2\u003e\n                \u003cul class=\"mt-2 ms-4\"\u003e\n                    \u003cli class=\"checked\"\u003eGit\u003c/li\u003e\n                    \u003cli\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel\" target=\"_blank\"\u003eGitHub\u003c/a\u003e\u003cspan class=\"{{ checked }}\"\u003e\u003c/span\u003e\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVercel\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVue\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eVite\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eTypeScript\u003c/li\u003e\n                    \u003cli class=\"checked\"\u003eTailwind\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eVitest\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003ePinia\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eHTTP APIs\u003c/li\u003e\n                    \u003cli class=\"unchecked\"\u003eTSC/ESLint\u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/div\u003e\n        \u003c/section\u003e\n\n        \u003chr class=\"my-2\"/\u003e\n\n        \u003cfooter class=\"flex flex-row justify-between\"\u003e\n            \u003cspan class=\"flex items-center\"\u003e\n                \u003cspan class=\"code\"\u003ev{{ version }}\u003c/span\u003e\n                \u003cspan class=\"text-gray-400 mx-2\"\u003e | \u003c/span\u003e\n                \u003cspan class=\"text-sm text-gray-400\"\u003e\u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel/blob/main/README.md\" target=\"_blank\"\u003eDocumentation\u003c/a\u003e\u003c/span\u003e\n            \u003c/span\u003e\n            \u003cspan class=\"code text-gray-400\"\u003ebuilt: {{ buildTimestamp }}\u003c/span\u003e\n        \u003c/footer\u003e\n    \u003c/article\u003e\n\u003c/template\u003e\n```\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.5\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/46aa7d3dba11970a1dd29ef800cc5fbb2c7caf87) | [deployment](https://defaultwebapp-3hi5g5q53-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Graphics\nLet's bring some shapes and colors into our page, e.g. by adding some logos.\nFor (minor) visual content, vector graphics (e.g. SVG files) and vector graphics-based fonts are preferred over binaries.\n\n\n#### By file\nLet's start at the top.\nDownload the SVG logo of git from https://git-scm.com/downloads/logos.\nAdd it to the `src/assets` folder.\n\nNow, add it to the \"Git\" list item:\n```vue\n...\n\u003cli class=\"checked flex items-center h-8\"\u003e\n    \u003cspan class=\"code\"\u003e[v0.0]\u003c/span\u003e\n    \u003cspan class=\"content-center ms-2\"\u003e\n        \u003ca href=\"https://git-scm.com\"\u003e\n            \u003cimg src=\"/src/assets/git-logo-2color.svg\" alt=\"Git\" height=\"44px\" width=\"44px\"/\u003e\n        \u003c/a\u003e\n    \u003c/span\u003e\n\u003c/li\u003e\n...\n```\nRegarding the `public` folder vs. the `src/assets` folder...\nFiles containing static content such as images, sounds, fonts, icons, etc.\nmay either be put in the `public` folder or the `src/assets` folder.\nThe `public` folder is a special folder that contains static files that Vite does **not** process.\nThe files in the `public` folder are copied directly to the build folder without any modification.\nAlso, they are publicly available via a URL.\nFiles in the `src/assets` folder will be processed by Vite, e.g., minification/compression –\nand embedded in the bundled version of the web app.\n\nBinary files usually go into the `public` folder.\nHowever, they may be placed in `src/assets` folder, so image manipulation can be used in the Vite build –\ne.g., by [this](https://github.com/ElMassimo/vite-plugin-image-presets) Vite plugin.\n\n\n#### By font\nThere are numbers of font sets available.\nA well-known font set is [Font Awesome](https://fontawesome.com).\nThere we find e.g., the GitHub logo:\nhttps://fontawesome.com/v6/icons/github?f=brands\u0026s=solid\n\nInstall:\n```shell\npnpm install @fortawesome/fontawesome-svg-core --save-dev\npnpm install @fortawesome/free-brands-svg-icons --save-dev\npnpm install @fortawesome/vue-fontawesome --save-dev\n```\n\nUpdate\n`main.ts`:\n```js\nimport {createApp} from 'vue'\nimport App from '../vue/App.vue'\nimport {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'\nimport '../styles/main.css'\n\ncreateApp(App)\n    .component('FontAwesomeIcon', FontAwesomeIcon)\n    .mount('#app')\n```\n\nand `App.vue`:\n```vue\n\u003cscript setup lang='ts'\u003e\n...\nimport {faGithub} from \"@fortawesome/free-brands-svg-icons\";\n...\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n    ...\n    \u003cli class=\"checked flex items-center h-8\"\u003e\n        \u003cspan class=\"code text-gray-400\"\u003e[v0.0]\u003c/span\u003e\n        \u003ca href=\"https://github.com/eirikt/default-webapp-vue-vite-vercel\" target=\"_blank\"\u003e\n            \u003cFontAwesomeIcon v-bind:icon=\"faGithub\"\n                             class=\"text-xl translate-y-0.5 ms-2\"\n            /\u003e\n            \u003cspan class=\"ms-1.5\"\u003eGitHub\u003c/span\u003e\n        \u003c/a\u003e\n    \u003c/li\u003e\n    ...\n    \u003cfooter class=\"flex justify-between\"\u003e\n        \u003cspan class=\"flex items-center\"\u003e\n            \u003cspan class=\"code\"\u003ev{{ version }}\u003c/span\u003e\n                \u003cspan\u003e\n                    \u003ca href=\"//github.com/eirikt/default-webapp-vue-vite-vercel/blob/main/README.md\" target=\"_blank\"\u003e\n                        \u003cFontAwesomeIcon v-bind:icon=\"faGithub\"\n                                         class=\"mx-3 text-xl text-gray-400\"\n                        /\u003e\n                    \u003c/a\u003e\n                \u003c/span\u003e\n        \u003c/span\u003e\n        \u003cspan class=\"code text-gray-400\"\u003ebuilt: {{ buildTimestamp }}\u003c/span\u003e\n    \u003c/footer\u003e\n\u003c/template\u003e\n```\nSee: https://docs.fontawesome.com/web/use-with/vue\n\n\n#### By embedding\nGo to https://vercel.com/geist/icons, and copy e.g., the Vercel logo as an SVG.\nPaste it directly in `App.vue`, alter the dimension, and maybe the position a little bit:\n```vue\n\u003ctemplate\u003e\n    ...\n    \u003cli class=\"checked flex items-center h-8\"\u003e\n        \u003cspan class=\"code text-gray-400\"\u003e[v0.0]\u003c/span\u003e\n        \u003ca href=\"https://vercel.com\" class=\"flex items-center\" target=\"_blank\"\u003e\n            \u003csvg class=\"ms-2\"\n                 data-testid=\"geist-icon\"\n                 stroke-linejoin=\"round\"\n                 viewBox=\"0 0 16 16\"\n                 height=\"18px\"\n                 width=\"18px\"\n                 style=\"color: currentcolor;\"\n            \u003e\n                \u003cpath fill-rule=\"evenodd\"\n                      clip-rule=\"evenodd\"\n                      d=\"M8 1L16 15H0L8 1Z\"\n                      fill=\"currentColor\"\n                /\u003e\n            \u003c/svg\u003e\n            \u003cspan class=\"ms-1\"\u003eVercel\u003c/span\u003e\n        \u003c/a\u003e\n    \u003c/li\u003e\n    ...\n\u003c/template\u003e\n```\n\n\n#### By Vue component\nSVG files may easily be turned into a Vue SFC.\nCreate the SFC file for the Git logo:\n```shell\nNew-Item ./src/vue/components/GitLogo.vue\n```\nThen copy the entire SVG file content into a `\u003ctemplate\u003e` block.\nAlso, values in the SVG file can be parameterized, like:\n```vue\n\u003cscript setup lang='ts'\u003e\n...\nconst props = defineProps(['height', 'width'])\n...\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n    ...\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" v-bind:height=\"height\" v-bind:width=\"width\" viewBox=\"0 0 219 92\"\u003e\n    ...\n\u003c/template\u003e\n```\nNow the component can be included with, e.g.:\n```xml\n\u003cGitLogo height=\"2rem\"\n         width=\"2.5rem\"\n         class=\"ms-2\"\n/\u003e\n```\nMore on creating SFCs with properties and events below.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.6\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/731a0c862a44541c10e7cacd89e8633d77b2d724) | [deployment](https://defaultwebapp-j8lbsjmhg-eirik-torskes-projects.vercel.app/) ]\u003c/sub\u003e\n\n...\n\n\nAdd the rest of the logo icons using one of the strategies above.\nSource the SVG- or image logos from the various component providers:\n- https://vercel.com/geist/icons/\n- https://github.com/vitest-dev/vitest/blob/main/docs/public/logo.svg\n- https://tailwindcss.com/brand/\n- https://www.typescriptlang.org/branding/\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.7\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/27f0e45b4b930f8b1ba83d77be761ca100b677d3) | [deployment](https://defaultwebapp-j8lbsjmhg-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.8\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/126af16dceb2eddf6f656e9b592e80bf85e7c75d) | [deployment](https://defaultwebapp-44u5gjzgm-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.9\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/b6d834acb7b8604f71834519570fa427de84c5ff) | [deployment](https://defaultwebapp-1stowfnur-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n...\n\n\n#### Backgrounds\nAnother way of bringing some more \"life\" into our web app is by using background gradient colors,\ne.g, via [Tailwind](https://tailwindcss.com/docs/gradient-color-stops).\nSome neat \"gradient generators\" are available, like:\n- https://www.creative-tim.com/twcomponents/gradient-generator\n- https://www.tailwindgradient.com\n- https://tailscan.com/gradients\n- https://gradienty.codes\n\nAlso, a very \"visual\" trick is background *images*,\nwhich Tailwind has [good support](https://tailwindcss.com/docs/background-image) for.\nTogether with backdrop effects like\n[blur](https://tailwindcss.com/docs/backdrop-blur), and\n[opacity](https://tailwindcss.com/docs/backdrop-opacity),\nvery nice backgrounds can be crafted.\n\nAs we will play around with background colors *dynamically*, in a short while–we won't use this functionality.\n\n...\n\n\nWe will only add a simple gradient to our header,\na silly from-\u003cspan style=\"color:red;\"\u003ered\u003c/span\u003e-to-\u003cspan style=\"color:green;\"\u003egreen\u003c/span\u003e (\"false-to-true\") effect.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.4.10\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/d36738ddf13f2e7cc64d40b2cd3d89786adc38e5) | [deployment](https://defaultwebapp-1stowfnur-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n---\n\n\n### Favicon\nWhile we are in the process of adding files to the `public` folder –\nlet us have a quick look at the internal well-being of our web app...\nClick \u003ckbd\u003eF12\u003c/kbd\u003e in the browser running our Vite development server.\nThis will bring up the Browser Developer Tools window.\nNow, as a test, do a hard browser refresh: \u003ckbd\u003eCTRL\u003c/kbd\u003e+\u003ckbd\u003eF5\u003c/kbd\u003e\nThere are (annoying) `404 Not Found` response errors in the browser console, complaining about missing favicon.\n\nThe days of just having a simple tiny `favicon.ico` file are long gone.\nSeveral image formats and types but also configuration files (e.g.: `browserconfig.xml` and `manifest.json`) are now necessary to have a nice favicon for all the available devices and screend out there.\nYou can generate your favicon files using the generator at https://realfavicongenerator.net.\nIt provides all the necessary files, also code snippets for updating our `\u003chead\u003e` element.\nFor my favicon, I just picked one from https://favicon.io/emoji-favicons/, namely https://favicon.io/emoji-favicons/pear/.\n\nThere are several more issues flagged by the browser...\nbut don't worry – we'll deal with those in due course, most of them, at least.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.5.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/9428e40de020cceed5b3600369d97643d6d67452) | [deployment](https://defaultwebapp-fpwn1yo64-eirik-torskes-projects.vercel.app/) ]\u003c/sub\u003e\n\n\n### Robots.txt\n[\"robots.txt\"](https://en.wikipedia.org/wiki/Robots.txt) is the filename used for implementing the Robots Exclusion Protocol –\na standard, used by websites, to indicate to visiting web crawlers and other web robots which portions of the website they are allowed to visit.\n\nThe \"robots.txt\" file can be used in conjunction with [sitemaps](https://en.wikipedia.org/wiki/Sitemaps),\nanother robot inclusion standard for websites.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.5.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/628e53bc01a980d96ea6aa2a8e55604150af3e74) | [deployment](https://defaultwebapp-m5bokp3mw-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Humans.txt\n[\"humans.txt\"](https://humanstxt.org) is an attempt to \"balance\" the `robots.txt` file with a more human-oriented \"info-file\".\n*\"robots.txt\" is for machines, \"humans.txt\" is for humans*, as the world wide web is intended for.\nIt is a place for expressing gratitude to authors of all tools that are helpful when creating your software projects.\n\nIt has gained little traction, it seems, but we'll include our own anyway.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.5.3\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/420cb311bf3067c449dad0c12910c8867ec4d1cf) | [deployment](https://defaultwebapp-lqsxf0qaw-eirik-torskes-projects.vercel.app) ]\u003c/sub\u003e\n\n\n### Security.txt\nAnother initiative in this webapp \"info-file area\" is \"security.txt\".\n[\"security.txt\"](https://en.wikipedia.org/wiki/Security.txt) is a standard for website security information that allows security researchers to report security vulnerabilities easily.\n\nThe standard prescribes a `security.txt` text file in the [well known location](https://en.wikipedia.org/wiki/Well-known_URI),\nsimilar in syntax to \"robots.txt\" but intended to be machine- and human-readable,\nfor those wishing to contact a website's owner about security issues.\n\nHave a look at [Google's](https://www.google.com/.well-known/security.txt) and [GitHub's](https://github.com/.well-known/security.txt) \"security.txt\" files.\n\nConsider adding this file as well – depending on the nature of your web app.\n\n\n### Ads.txt\nAnother initiative in this webapp \"info-file area\" is \"ads.txt\".\n[\"ads.txt\"](https://en.wikipedia.org/wiki/Ads.txt) is a text file that companies can host on their web servers,\nlisting the other companies authorized to sell their products or services.\nThis is designed to allow online buyers to check the validity of the sellers from whom they buy,\nfor the purposes of internet fraud prevention.\n\n\n---\n\n\nOur web app looks better now, but we have a bug.\nThe build timestamp is not a build timestamp at all; it just shows the current time, and that is not very interesting.\nJust check it by pushing \u003ckbd\u003eF5\u003c/kbd\u003e a couple of times.\n\nThe timestamp cannot be defined within the `App.vue` component,\nwhich has its logic executed and content rendered for every state change.\nNo, it must be defined within the build step, and somehow be included in the built artifacts.\n\n...\n\n\nIn `App.vue`, replace:\n```vue\n\u003cscript setup lang='ts'\u003e\n...\nconst buildTimestamp = new Date()\n...\n\u003c/script\u003e\n```\nwith:\n```vue\n\u003cscript setup lang='ts'\u003e\n...\nconst mode: string = 'APP_MODE'\nconst buildTimestamp: string = 'APP_BUILD_TIME'\n...\n\u003c/script\u003e\n```\nNow, we have a clear view of what to do...\n\nAlso, insert a new footer element for the application mode –\nbetween the version and the build timestamp:\n```vue\n\u003ctemplate\u003e\n    ...\n    \u003cspan v-if=\"mode\" class=\"capitalized warning\"\u003e{{ mode }}\u003c/span\u003e\n    ...\n\u003c/template\u003e\n```\n\nIn `vite.config.ts`, use the full function signatures for the required `defineConfig` function:\n```typescript\nimport type {ConfigEnv, UserConfig} from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\nexport default function defineConfig(configEnv: ConfigEnv): UserConfig {\n    return {\n        ...\n    } satisfies UserConfig\n}\n```\nHave a look at the types and function signatures.\n(\u003ckbd\u003eCTRL\u003c/kbd\u003e+\u003ckbd\u003eB\u003c/kbd\u003e in IntelliJ IDEA.)\n\nIt is the `UserConfig.define` property which we should manipulate:\n```typescript\n/**\n * Define global variable replacements.\n * Entries will be defined on `window` during dev and replaced during build.\n */\ndefine?: Record\u003cstring, any\u003e;\n```\n\nEnvironmental Variables can be obtained from `process.env` as usual.\nNote that Vite doesn't load `.env` files by default as the files to load can only be determined after evaluating the Vite config,\nfor example, the `root` and `envDir` options affect the loading behavior.\nHowever, you can use the exported `loadEnv` helper to load the specific `.env` file if needed.\n\nAdd:\n```typescript\nimport {loadEnv} from 'vite'\n...\nexport default function defineConfig(configEnv: ConfigEnv): UserConfig {\n    const env: Record\u003cstring, string\u003e = loadEnv(configEnv.mode, process.cwd(), '')\n    ...\n}\n```\n\nDeclare global variables `APP_MODE` and `APP_BUILD_TIME` by:\n```typescript\nconst define: Record\u003cstring, string\u003e = {\n    'APP_MODE': (configEnv.mode == 'production')\n        ? JSON.stringify('')\n        : JSON.stringify(configEnv.mode + ' (builder: ' + env.COMPUTERNAME + ')'),\n\n    'APP_BUILD_TIME': JSON.stringify(new Date())\n}\n```\nThen include the `define` property in the return value,\nwhich is handed to Vite which add its properties to the `window` object.\n\nNow, update `App.vue` \"script\" part with:\n```vue\n...\n\u003cscript setup lang='ts'\u003e\n    ...\n    const mode: string = APP_MODE\n    const buildTimestamp: string = APP_BUILD_TIME\n    ...\n\u003c/script\u003e\n```\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.6.0\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/45176a9a1f3568d6eac85527d2be49d129e78020) | [deployment](https://defaultwebapp-jd6hl27fb-eirik-torskes-projects.vercel.app/) ]\u003c/sub\u003e\n\n...\n\n\nWe have fixed the built timestamp bug,\nbut the timestamp looks a little bit ...technical and \"committee-like\".\nLet us format the build timestamp to a more readable representation.\n\n\n### Testing with Vitest\nDynamic analysis, formal specifications, testing ...it's all about the same;\nincrease the conciseness, predictability, and overall quality of the software.\n\n[Vitest](http://vitest.dev) seems to be the idiomatic choice when it comes to testing with Vite:\n```shell\npnpm install vitest --save-dev\npnpm install @vitest/coverage-v8 --save-dev\n```\n\nExtend the scripts with test-related ones:\n```json\n\"scripts\": {\n  ...\n  \"test\": \"vitest run\",\n  \"test:coverage\": \"vitest run --coverage\",\n  \"test:report\": \"vitest --ui --coverage\",\n  ...\n},\n```\nExecute the test script right away:\n```shell\npnpm test\n```\nNo test yet available – that is correct.\n\n\n...\n\n\nWe will use date formatting as a (trivial, albeit ubiquitous) example/issue.\nThe build timestamp looks a bit too ...technical, with it's 'T' and 'Z' and ...\nAlso, while at it;\nthe `Date` object has been a long-standing pain point in ECMAScript.\nIt has a number of challenges:\n- lack of immutability\n- lack of support for time zones\n- lack of support for use cases that require dates only or times only\n- ...and many other challenges.\n\nLet us use the new proposal for dates in ECMAScript, [Temporal](https://tc39.es/proposal-temporal/):\n```shell\npnpm install temporal-polyfill --save-dev\n```\n\nCreate formal specification/test file:\n```shell\nNew-Item .\\src\\scripts\\iso8601.spec.ts\n```\n\nAdd a Vitest test/specification:\n```typescript\ntest('Should pretty-print time stamp', () =\u003e {\n    const buildTime: Temporal.Instant = Temporal.Instant.from('2024-09-20T19:28:01.000999Z')\n    expect(prettyprint(buildTime)).toBe('2024-09-20 19:28 (UTC)')\n})\n```\nUse the IDE capabilities for adding import statements, and also starting on the implementation.\nIf it isn't up for the task, add this in `iso8601.spec.ts`:\n```typescript\nimport {expect, test} from 'vitest'\nimport {Temporal} from 'temporal-polyfill'\nimport {prettyprint} from './iso8601'\n```\n\nTest these by:\n```shell\npnpm test\n```\n...and follow the directions:\n```shell\nNew-Item .\\src\\scripts\\iso8601.ts\n```\n\nAdd implementation, e.g.,:\n```typescript\nimport {Temporal} from 'temporal-polyfill'\n\nexport function prettyprint(instant: Temporal.Instant): string {\n    if (!instant) {\n        return ''\n    }\n    return prettyprintZonedDateTime(instant.toZonedDateTimeISO('UTC'))\n}\n\nexport function prettyprintZonedDateTime(zonedDateTime: Temporal.ZonedDateTime): string {\n    if (!zonedDateTime) {\n        return ''\n    }\n    return zonedDateTime.year\n        + '-' + zonedDateTime.month.toString().padStart(2, '0')\n        + '-' + zonedDateTime.day.toString().padStart(2, '0')\n        + ' '\n        + zonedDateTime.hour.toString().padStart(2, '0')\n        + ':' + zonedDateTime.minute.toString().padStart(2, '0')\n        + ' (' + zonedDateTime.getTimeZone().toString() + ')'\n}\n```\n\nAlso, execute test coverage:\n```shell\npnpm test:coverage\n```\n\nHave a look at the test coverage GUI tool:\n```shell\npnpm test:report\n```\nInstall needed dependencies.\n\nWe can clearly see that we still have work to do regarding test coverage...\n\nFor more info about test coverage in Vite, see:\nhttps://vitest.dev/guide/coverage\n\n...\n\n\nWhen the specifications are met/the tests go green,\nwe can safely add this little improvement to the `vite.config.ts` file:\n```typescript\nimport {Temporal} from 'temporal-polyfill'\nimport {prettyprint} from './src/scripts/iso8601'\n...\nAPP_BUILD_TIME: JSON.stringify(prettyprint(Temporal.Now.instant()))\n```\nNow, mark Vitest tool as \"integrated\" in `App.vue`.\n Also, add the `coverage` folder to the `clean` script:\n```json\n\"clean\": \"pnpm rimraf --verbose coverage dist\",\n```\n\nUpgrade Vitest to \"checked\" in `App.vue`, and execute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.6.1\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/6f686055f5e00531fe61d9f530384fad4e79d1e5) | [deployment](https://defaultwebapp-azp561omu-eirik-torskes-projects.vercel.app/) ]\u003c/sub\u003e\n\n\n---\n\n\nFormal specifications/testing is also known as [*dynamic* source code analysis](https://en.wikipedia.org/wiki/Dynamic_program_analysis).\nAlso, we have [*static* source code analysis](https://en.wikipedia.org/wiki/Static_program_analysis) –\nwhich scans the source code for issues and weaknesses.\n\n\n### Upgrade scripts for code dependencies\nOne type of static source code analysis tools is *managing the source code dependencies*.\nWe will use several source code dependencies tools, one of them must be installed:\n```shell\npnpm install npm-check-updates --save-dev\n```\nThe other ones are included in npm/pnpm.\n\nAdd the following in the \"script\" block in `package.json`:\n```json\n\"dependencies:check:1\": \"pnpm outdated --depth=0\",\n\"dependencies:check:2\": \"pnpm npm-check-updates\",\n\"dependencies:check:3\": \"pnpm audit\",\n\"dependencies:check\": \"pnpm \\\"/^dependencies:check:.*/\\\"\",\n\"dependencies:upgrade:1\": \"pnpm upgrade\",\n\"dependencies:upgrade:2\": \"pnpm npm-check-updates --upgrade\",\n\"dependencies:upgrade:3\": \"pnpm audit --fix\",\n\"dependencies:upgrade\": \"pnpm \\\"/^dependencies:upgrade:.*/\\\"\",\n```\nExecute all of the `dependencies:check:*` scripts.\nCompare the reports.\n\nExecute all of the `dependencies:upgrade:*` scripts, until none of the yield any issues.\n\nIf there are issues with [peer dependencies](https://medium.com/@ruben.alapont/advanced-npm-working-with-peer-dependencies-b7c43aa852d8) found,\ninstall those as well, e.g.:\n```shell\npnpm install @types/node --save-peer\npnpm install typescript --save-peer\n```\n\nComplete the routine with a final `pnpm dependencies:check`, which now should be \"nice and clean\".\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.6.2\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/400592f678e33a5f4b3d49fca989332228f2b124) | no visual changes ]\u003c/sub\u003e\n\n...\n\n\nThe other type of static source code analysis is *code quality*.\nSuch tools have been sort of a sidekick,\nand have, in my view, seldom been given any important place in software build pipelines.\nWith the advent of artificial intelligence, this may be changing, as the tools are improving their overall quality.\n\nWe will use a couple of such tools.\nThe first one is the TypeScript compiler itself, with a Vue extension.\n\n\n### Static code analysis with TypeScript compiler\nA type system like TypeScript can detect many common errors via static analysis at build time.\nThis reduces the chance of runtime errors in production,\nand also allows us to more confidently refactor code in large-scale applications.\n\nVue is written in TypeScript itself and provides first-class TypeScript support.\nAll official Vue packages come with bundled [type declaration files](https://en.wikipedia.org/wiki/TypeScript#Declaration_files)\n(files having suffix `d.ts`) that should work out-of-the-box (module `@vue/tsconfig`).\n\nFor Vue SFCs, use the `vue-tsc` [CLI](https://en.wikipedia.org/wiki/Command-line_interface) utility for generating type declarations, and type checking.\n`vue-tsc` is a wrapper around `tsc`, TypeScript's own command line interface.\nIt works largely the same as `tsc` except that it supports Vue SFC files in addition to regular TypeScript files.\n\nInstall:\n```shell\npnpm install @vue/tsconfig --save-dev\npnpm install vue-tsc --save-dev\n```\n\nCreate `tsconfig.json` file:\n```shell\nNew-Item .\\tsconfig.json\n```\nAdd:\n```json\n{\n    \"extends\": [\n        \"@vue/tsconfig/tsconfig.dom.json\"\n    ],\n    \"compilerOptions\": {\n        \"noEmit\": false\n    }\n}\n```\nWe are mainly just extending Vue's recommended TypeScript configuration:\nhttps://github.com/vuejs/tsconfig/blob/main/tsconfig.dom.json,\nbut, reactivating the \"emit\" property as we will use `vue-tsc` for generating type declarations.\n\n(More about the TypeScript compiler tool [here](https://www.typescriptlang.org/docs/handbook/compiler-options.html)\nand [here](https://www.typescriptlang.org/tsconfig/).\nMore about using Vue with TypeScript, [here](https://vuejs.org/guide/typescript/overview.html).)\n\nRun:\n```shell\npnpm vue-tsc --noEmit\n```\nThere are some issues...\n\nIn `App.vue` the Vue-aware TypeScript compiler complains about the `APP_MODE` and `APP_BUILD_TIME` values.\nThat is quite understandable as they are expected to exist as global variables, but Vue does not know about them.\nA simple trick is to add them in a little manually hand-crafted TypeScript type declaration file.\nAs `APP_MODE` and `APP_BUILD_TIME` are regarded as \"environment variables\", we can name it `env.d.ts`.\nExecute:\n```shell\nSet-Content -Path .\\src\\scripts\\env.d.ts -Value \"declare const APP_MODE: string, APP_BUILD_TIME: string\"\n```\n\nAlso, we have an issue in `iso8601.ts`: \"error TS2722: Cannot invoke an object which is possibly 'undefined'\".\nThat can be solved by replacing `zonedDateTime.getTimeZone().toString()` with `zonedDateTime.getTimeZone().id`.\n\nRun the compiler again:\n```shell\npnpm vue-tsc --noEmit\n```\nAll problems should now have been resolved.\n\nExecute the deployment routine, described above.\n\n\u003csub\u003e[ \u003ccode\u003ev0.6.3\u003c/code\u003e: [commit](https://github.com/eirikt/default-webapp-vue-vite-vercel/commit/400592f678e33a5f4b3d49fca989332228f2b124) | no visual changes ]\u003c/sub\u003e\n\n\n### Static code analysis with ESLint\n[ESLint](https://eslint.org) is the idiomatic choice for statical analysis for web projects.\nDoing static source code analysis with the motivation of finding possible problems is called [linting](https://en.wikipedia.org/wiki/Lint_(software)).\n\nInstall ESLint with support for TypeScript and Vue:\n```shell\npnpm install eslint --save-dev\npnpm install @eslint/js --save-dev\npnpm install @eslint/compat --save-dev\npnpm install typescript-eslint --save-dev\npnpm install eslint-plugin-vue --save-dev\n```\n\nCreate `tsconfig.json` file:\n```shell\nNew-Item .\\eslint.config.js\n```\nIt is possible with a TypeScript configuration file, but we'll leave that for now –\navoiding any [abstraction leakages](https://en.wikipedia.org/wiki/Leaky_abstraction)...\n\nThe `eslint.config.js` file may contain one or more ESLint [configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects).\nLet us be bold, and add *all* linting rules (as configuration objects) for *all* ECMAScript, TypeScript, and Vue files:\n```javascript\nimport eslintJs from '@eslint/js';\nimport eslintTs from 'typescript-eslint';\nimport eslintVue from 'eslint-plugin-vue';\n\nexport default [\n\n    // All JavaScript linting rules\n    eslintJs.configs.all,\n\n    // All TypeScript linting rules\n    ...eslintTs.configs.all,\n    {\n        languageOptions: {\n            parserOptions: {\n                projectService: true\n            }\n        }\n    },\n\n    // Vue \"essential\" linting rules\n    ...eslintVue.configs['flat/essential'],\n    {\n        languageOptions: {\n            parserOptions: {\n                parser: eslintTs.parser\n            }\n        }\n    }\n];\n```\nWhen more than one configuration object matches a given filename,\nthe configuration objects are merged with later objects overriding previous ones when there is a conflict.\n\nTry it out:\n```shell\npnpm eslint\n```\n...a lot of issues are reported – some 40 of them...\n\nAlbeit, we'll leave it for now and move on.\nWe'll even \"burn the bridges\", and include the linting in the build, as a prerequisite for building our webapp.\n\nIn `package.json`, add the following scripts:\n```json\n\"lint:vue-tsc\": \"pnpm vue-tsc --noEmit\",\n\"lint:eslint\": \"pnpm eslint\",\n\"lint\": \"pnpm \\\"/^lint:.*/\\\"\",\n```\n\nThen update the build scripts:\n```json\n\"build:staging\": \"pnpm clean \u0026\u0026 pnpm lint \u0026\u0026 pnpm vite build --mode staging\",\n\"build:production\": \"pnpm clean \u0026\u0026 pnpm lint \u0026\u0026 pnpm vite build\",\n```\n\n\u003csub\u003e[ \u003ccode\u003ev0.6.4\u003c/code\u003e: [commit]() | no visual changes, AND the build is broken ]\u003c/sub\u003e\n\n\n#### Mending the reported static code analysis issues\nThe build is broken.\nWe have work to do.\n\nFirstly, let us try to resolve some of them by:\n```shell\npnpm eslint --fix\n```\nHave a look at the suggested changes.\nWere any of the suggested fixes useful? Keep those, revert the rest.\n\nSome issues that we will fix:\n```\neslint.config.js was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject\npostcss.config.js was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject\n```\nIn the TypeScript ESLint configuration object, add:\n```javascript\nlanguageOptions: {\n    parserOptions: {\n        projectService: {\n            allowDefaultProject: [\n                'eslint.config.js',\n                'postcss.config.js'\n            ]\n        }\n    }\n}\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feirikt%2Fdefault-webapp-vue-vite-vercel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feirikt%2Fdefault-webapp-vue-vite-vercel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feirikt%2Fdefault-webapp-vue-vite-vercel/lists"}