Vite, Next Generation Frontend Tooling

Some Background

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My App</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/3.2.5/stylesheets/foundation.min.css" />
    <!-- More css -->
    <style>
      p { color: red; }
    </style>
    <!-- PRODUCTION  --
    <script src="https://code.jquery.com/jquery-3.6.3..min.js"></script>
    -->
    <script src="https://code.jquery.com/jquery-3.6.3.js"></script>
    <!-- More scripts -->
    <script src="main.js"></script>
  </head>
  <body>
    <div class="view-container">
      <p>Loading...</p>
    </div>
    <!-- More scripts -->
    <script>
      // When document is ready call to main function
      $(function () { main(); });
    </script>
  </body>
</html>
# installs the project dependencies listed in bower.json

bower install
# registered package

bower install jquery
# GitHub shorthand

bower install desandro/masonry
# Git endpoint

bower install git://github.com/user/package.git
# URL

bower install http://example.com/script.js

Bower

.
├── .bowerrc
├── .gitignore
├── .jshintrc
├── Gruntfile.js // gulp.js
├── README.md
├── bower.json
├── build.sh
├── package.json
├── bower_components
├── node_module
├── public
│	├── index.html
│	├── main.js
│	├── main.css
│	└── assets
└── src
    ├── config
    ├── img
    ├── scripts
    ├── styles
    └── views

The project

Webpack

The idea

El objetivo principal de Webpack es tomar los activos de su proyecto web y agruparlos en una pequeña cantidad de archivos.

 

Webpack sigue las declaraciones imports/require en el código para incluir solo los archivos que realmente se necesitan.

 

Además, a menudo "tree-shaking", lo que puede eliminar bloques de código individuales.

Community templates

Vite, Next Generation Frontend Tooling

Why Vite ?

 It’s a build tool that comes with a dev server, and it bundles your code for production.

Vite makes the feedback loop speed during development super fast when you make changes.

Vite is using Hot Module Replacement (HMR) to update changed code while you’re developing your project.

Vite is leveraging native ES modules and native ESM dynamic import, to allow your code to be injected into the browser as needed.

Why Vite ?

 It’s a build tool that comes with a dev server, and it bundles your code for production.

Vite makes the feedback loop speed during development super fast when you make changes.

Vite authors our code as native ES Modules, which modern browsers support as a way to load your JavaScript. Plus, if you have large dependencies, Vite will pre-bundle them to reduce the number of requests that the browser must do to the web server.

How Does Vite js Work?

Vite is using Hot Module Replacement (HMR) to update changed code while you’re developing your project.

Vite is leveraging native ES modules and dynamic ESM modules to allow your code to be injected into the browser as needed.

Vite and Production

Vite itself doesn’t actually package your project. It benefits from ESbuild's efficiency to improve JavaScript compilation and Rollup's capabilities to compile and package code.

This will build the project, producing several files (with checksums to break the cache)

NPM Dependency Resolving and Pre-Bundle them to reduce the number of requests that the browser must do to the web server.

Async Chunk Loading Optimization

Rollup often generates "common" chunks - code that is shared between two or more other chunks.

Vite's optimization will trace all the direct imports to completely eliminate the roundtrips regardless of import depth.

Getting Started

Getting Started

$ npm create vite@latest

Need to install the following packages:
  create-vite@4.0.0
Ok to proceed? (y) y
✔ Project name: … vite-project
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Scaffolding project in ..../vite-project

Done. Now run:

  cd vite-project
  npm install
  npm run dev

Getting Started

$  tree -L 2
.
├── .gitignore
├── README.md
├── index.html
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── assets
│   ├── components
│   ├── main.js
│   └── style.css
└── vite.config.js

$ npm install

Getting Started

// package.json

{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite", // start dev server, vite server or vite dev
    "build": "vite build", // build for production
    "preview": "vite preview" // locally preview production build
  },
  "dependencies": {
    "vue": "^3.2.45"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.0.0",
    "vite": "^4.0.0"
  }
}

Getting Started

// vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
})

Basic Vite config (if it is necessary)

Static Asset

import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl

Static Asset Handling

imgUrl will be /img.png during development, and become /assets/img.2d8efhg.png in the production build.

<script type="module" src="./src/index.js"></script>
<link rel="stylesheet" href="./style.css" />
<script type="module" crossorigin src="/assets/index.1d9c2da7.js"></script>
<link rel="stylesheet" href="/assets/index.03175e43.css">

Static Asset Handling

import.meta.url is a native ESM feature that exposes the current module's URL.

const imgUrl = new URL('./img.png', import.meta.url).href

document.getElementById('hero-img').src = imgUrl

Importing Asset as String

import shaderString from './shader.glsl?raw'

Does not work with SSR

CSS Modules

import classes from './example.module.css'
document.getElementById('foo').className = classes.red

document.getElementById('boo').className = classes.applyColor
/* example.module.css */
.red {
  color: red;
}

.apply-color {
  background-color: blue;
}

JSON

// import the entire object
import json from './example.json'
// import a root field as named exports - helps with tree-shaking!
import { field } from './example.json'

Import

Vite also supports dynamic import with variables.

const module = await import("./dir/" + file + ".js")

Note that variables only represent file names one level deep.

WebAssembly

Pre-compiled .wasm files can be imported with ?init - the default export will be an initialization function that returns a Promise of the wasm instance.

import init from './example.wasm?init'

// WebAssembly.instantiate
init().then((instance) => {
  instance.exports.test()
})

Web Workers

import MyWorker from './worker?worker'

const worker = new MyWorker()

A web worker script can be directly imported by appending ?worker or ?sharedworker to the import request.

Import

// code produced by vite
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js'),
}
const modules = import.meta.glob('./dir/*.js')
// code produced by vite
const modules = {
  './dir/foo.js': 'export default "foo"\n',
  './dir/bar.js': 'export default "bar"\n',
}
const modules = import.meta.glob('./dir/*.js', { as: 'raw' })

Glob import

Plugin

$ npm add -D @vitejs/plugin-legacy

Adding a Plugin

  • Alias

  • User plugins with enforce: 'pre'

  • Vite core plugins

  • User plugins without enforce value

  • Vite build plugins

  • User plugins with enforce: 'post'

  • Vite post build plugins (minify, manifest, reporting)

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
      {
        ...legacy({
      	  targets: ['defaults', 'IE 11'],
    	}),
        apply: 'build', // or "server"
        // enforce: 'pre', // or "post" (according to core plug-in)
      }
    }],
})

Adding a Plugin

Simple Examples

const fileRegex = /\.(my-file-ext)$/

export default function myPlugin() {
  return {
    name: 'transform-file',

    transform(src, id) {
      if (fileRegex.test(id)) {
        return {
          code: compileFileToJS(src),
          map: null, // provide source map if available
        }
      }
    },
  }
}

Virtual Modules Convention

export default function myPlugin() {
  const virtualModuleId = 'virtual:my-module'
  const resolvedVirtualModuleId = '\0' + virtualModuleId

  return {
    name: 'my-plugin', // required
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "from virtual module"`
      }
    },
  }
}
import { msg } from 'virtual:my-module'
console.log(msg)

Building for Production

Simply run the vite build command. By default, it uses <root>/index.html as the build entry point.

Building for Production

 Using vite build --watch for rebuild on files changes.

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // https://rollupjs.org/guide/en/#big-list-of-options
      input: {
        main: resolve(__dirname, 'index.html'),
        nested: resolve(__dirname, 'nested/index.html'),
      },
    },
  },
})

Customizing the Build

// vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'lib/main.js'),
      name: 'MyLib',
      fileName: 'my-lib',
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
})

Customizing the Build

Library Mode

Env Variables and Modes

Vite exposes env variables on the special import.meta.env[key]

 

  • import.meta.env.MODE

  • import.meta.env.BASE_URL

  • import.meta.env.PROD

  • import.meta.env.DEV

  • import.meta.env.SSR

Env Variables

Vite uses dotenv to load additional environment variables from the following files in your environment directory.

Env Variables

.env                       # loaded in all cases
.env.local               # loaded in all cases, ignored by git
.env.[mode]            # only loaded in specified mode
.env.[mode].local   # only loaded in specified mode, ignored by git

Only variables prefixed with VITE_ are exposed.

Env Variables

# .env

VITE_SOME_KEY=123
DB_PASSWORD=foobar
console.log(import.meta.env.VITE_SOME_KEY) // 123
console.log(import.meta.env.DB_PASSWORD) // undefined

const myVar = "VITE_SOME_KEY"
console.log(import.meta.env[myVar]) // undefined

Vite uses dotenv-expand to expand variables out of the box.

Env Variables

# .env

KEY=123
NEW_KEY1=test$foo   # test
NEW_KEY2=test\$foo  # test$foo
NEW_KEY3=test$KEY   # test123

Env Variables

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(async({ command, mode }) => {
  const data = await asyncFunction();
  const appVersion = JSON.stringify(process.env.npm_package_version);
  
  return {
    // vite config
    define: {
        '__APP_VERSION__': appVersion,
        '__SOME_THING__': data.someData
    },
  }
})

Modes

# .env.production
VITE_APP_TITLE=My App
# .env.staging
VITE_APP_TITLE=My App (staging)

$ vite build --mode staging

$ vite build

Server-Side Rendering

Start SSR

<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>
- index.html
- server.js # main application server
- src/
  - main.js          # exports env-agnostic (universal) app code
  - entry-client.js  # mounts the app to a DOM element
  - entry-server.js  # renders the app using the framework's SSR API
$ vite build --ssr

Build a server

Project structure

Index.html

Client-server Communication

// vite.config.js
export default defineConfig({
  plugins: [
    {
      // ...
      configureServer(server) {
        server.ws.send('my:greetings', { msg: 'hello' })
      },
    },
  ],
})

Server to Client

// client side
if (import.meta.hot) {
  import.meta.hot.on('my:greetings', (data) => {
    console.log(data.msg) // hello
  })
}

Client-server Communication

// vite.config.js
export default defineConfig({
  plugins: [
    {
      // ...
      configureServer(server) {
        server.ws.on('my:from-client', (data, client) => {
          console.log('Message from client:', data.msg) // Hey!
          // reply only to the client (if needed)
          client.send('my:ack', { msg: 'Hi! I got your message!' })
        })
      },
    },
  ],
})

Client to Server

// client side
if (import.meta.hot) {
  import.meta.hot.send('my:from-client', { msg: 'Hey!' })
}