Skip to content

Vite

Vite describes itself as "a blazing fast frontend build tool powering the next generation of web applications". It has on demand file serving over native ESM and lightning fast hot module reloading. Sku supports Vite as an alternate to the Webpack bundler since v15.

Limitations

Vite support is currently only available for static applications (SSG). This means that only sku start and sku build are supported. sku serve is also available as it is bundler agnostic.

Planned deprecation of library mode

Building libraries with webpack is currently supported by sku. However, this feature is planned for deprecation and will not be supported with Vite. A migration guide for sku libraries will be provided in the future once the deprecation is finalised.

Prerequisites

WARNING

Before making any changes to your application, please ensure you have read this document in its entirety.

There are two critical prerequisites for migrating to Vite:

  1. Applications must be written in ESM
  2. Applications must use Vitest for running tests

Given Jest's current limitations with ESM, it is highly likely that both these prerequisites will need to be implemented at the same time.

It is highly recommended to implement, test and release these changes independently from the changes necessary to migrate to Vite.

Migrating to Vitest

Vitest is a testing framework that supports ESM out-of-the-box, integrates with the Vite ecosystem and has a similar API to Jest. These features make it a great replacement for Jest in sku applications, especially given Jest's current limitations with ESM. Due to these limitations, it's likely that you'll need to migrate to Vitest at the same time as (or prior to) migrating to ESM.

See sku's vitest documentation for how to migrate to vitest.

Migrating to ESM

Migrating to ESM involves two steps:

  1. Ensure your application declares itself as an ES module
  2. Ensure all application code uses ESM syntax for importing and exporting modules

Declaring an ES module

Declaring your package as an ES module involves adding "type": "module" to your package.json file:

diff
{
  "name": "my-sku-app",
+ "type": "module",
  "scripts": {
    "start": "sku start",
    "build": "sku build"
  },
  ...
}

While this change may seem small, it has a significant impact on how Node.js and TypeScript interpret your code: it signals that any code written in .js or .ts files should be treated as ESM.

You may have non-application code such as Node.js scripts or configuration files that will also be affected by this change. If these files contain CommonJS (CJS) syntax and you do not wish to convert them to ESM, you can keep them as CommonJS by using the .cjs or .cts file extensions. However, it is highly recommended to convert all code to ESM if possible.

Finding ESM code changes

After changing the repo to type: module, the eslint-cjs-to-esm package can be used to check for potential ESM code changes needed:

bash
npx eslint-cjs-to-esm "./src/**/*.{js,ts}" --rule "node/file-extension-in-import: off, file-extension-in-import-ts/file-extension-in-import-ts: off, import/extensions: off"

Common files that may need to be updated include:

The following sections detail changes that may be required to migrate CJS code to ESM.

ESM syntax

Most application code at SEEK is already written using ESM syntax, so it's unlikely you'll need to make many changes to your application. However, if you do need to convert some code to ESM, the primary change will be to ensure you are using the correct import syntax.

In ESM, modules are imported using the import keyword and exported using the export keyword:

diff
// named imports
-const { foo } = require('foo');
+import { foo } from 'foo';

// default imports
-const express = require('express');
+import express from 'express';

// named exports
-const SOME_CONSTANT = 'some value';
-module.exports = { SOME_CONSTANT };
+export const SOME_CONSTANT = 'some value';

// default exports
const ANOTHER_CONSTANT = '123';
-module.exports = ANOTHER_CONSTANT;
+export default ANOTHER_CONSTANT;

Import path file extensions

Typically, ESM resolution dictates that relative and absolute import specifiers must include a file extension, and that directory indexes (index.js files) must also be fully specified.

However, Vite can resolve these imports for you, so it is only necessary to include file extensions in import paths within non-application code.

TIP

By default, sku configures allowImportingTsExtensions: true in your tsconfig.json file. In situations where an explicit file extension is required, such as in a Node.js script, this setting allows you to import TypeScript files with a .ts extension instead of a .js extension, which can be a source of confusion for those new to ESM codebases.

Migrating to Vite

To configure sku to bundle your applications with Vite, configure bundler in your sku config:

typescript
// sku.config.ts
import type { SkuConfig } from 'sku';

export default {
  bundler: 'vite',
  ...
} satisfies SkuConfig;

Depending on your application, you may need no further changes to your codebase after this point in order to run your application with Vite.

Documented below is a list of differences between sku with webpack and sku with Vite.

TIP

If you encounter issues during migration that aren't listed below, please reach out in #sku-support so we can update this document.

Code splitting

Routes and components that take advantage of sku's code splitting API will need to update imports from sku/@loadable/component to @sku-lib/vite/loadable. A codemod is available to help with this migration:

bash
pnpm dlx @sku-lib/codemod transform-vite-loadable .

You will also need to install a separate library that provides Vite-compatible loadable APIs:

bash
pnpm add @sku-lib/vite

@sku-lib/vite/loadable relies on React's <Suspense /> component to load a fallback state. You can wrap a loadable component in a <Suspense /> component or provide a fallback option to the loadable function which will wrap it inside a <Suspense /> component for you:

tsx
import { Suspense } from 'react';
import { loadable } from '@sku-lib/vite/loadable';

const Home = loadable(() => import('./Home'), {
  fallback: <div>Loading Home...</div>,
});

export default () => (
  <div>
    <Home />
  </div>
);

Note that in order to use loadable with a fallback, your application must use the renderToStringAsync API. See the supporting react suspense documentation for more information.

Dev server middleware

The Vite dev server uses Connect as its server framework, as opposed to webpack which uses Express. As a result, the middleware API has changed - the middleware function now receives a Connect.Server instance that can be used to add middleware to the dev server.

Middleware can be added to the dev server via the use method on the server instance:

javascript
// devMiddleware.js
export default function (server) {
  server.use((req, res, next) => {
    // your middleware logic
    next();
  });

  // or use a path
  server.use('/api', (req, res, next) => {
    // your middleware logic
    next();
  });
}

NOTE

Currently only JavaScript middleware is supported.

CJS named imports

Importing named exports from CJS dependencies may result in an error:

SyntaxError: [vite] Named export 'someFunction' not found. The requested module 'someDependency' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'someDependency';
const {someFunction} = pkg;

There are a few options to resolve this issue:

  • Replace the dependency with native APIs
  • Upgrade the dependency to a version that supports ESM
  • Replace the dependency with an alternative that supports ESM

Failing those solutions, sku provides a compilePackages option that will compile the given modules as if they were part of your source code. This may affect build time, but allows Vite to handle certain CJS dependencies without throwing the error above. Use this option as a last resort:

typescript
// sku.config.ts
import type { SkuConfig } from 'sku';

export default {
  compilePackages: [
    'someDependency'
  ],
  ...
} satisfies SkuConfig;

Vite client types

If you require types for Vite's client-side APIs, such as import.meta.glob, or types for imported image assets, create a .d.ts file in your codebase:

typescript
// src/vite-env.d.ts

// eslint-disable-next-line spaced-comment
/// <reference types="sku/vite/client" />

Importing image assets

Vite provides built-in support for importing image assets as URLs. See the importing image assets docs for more info.

Migrating SVG imports

Importing SVG files with no query parameters has different behaviour in webpack and Vite. SVG imports within your application will need to be updated in order to function correctly with Vite.

IMPORTANT

Your application must be on at least sku v15.13.0 in order to use the raw, url and inline query parameters described below.

The simplest way to migrate is to add the raw query parameter to all SVG imports in your codebase, which will import the raw SVG markup as a string in both webpack and Vite. This can be done automatically with the svg-import-query-param codemod:

sh
pnpm dlx @sku-lib/codemod svg-import-query-param .

If you were manually constructing data: URLs from the imported SVG markup, you can instead use the url or inline query parameters to import the SVG as a URL or data URL respectively, removing the need to construct a data URL yourself:

diff
import { style } from '@vanilla-extract/css';
-import iconMarkup from './icon.svg?raw';

// URL of the SVG file
+import iconUrl from './icon.svg?url';
// or SVG data URL
+import iconUrl from './icon.svg?inline';

export const svgBackground = style({
-  backgroundImage: `url("data:image/svg+xml;base64,${Buffer.from(iconMarkup).toString('base64')}")`,
+  backgroundImage: `url("${iconUrl}")`,
});

Similar changes will need to be made in any libraries you consume that import SVG files. Consumers of these libraries may see inconsistent results when importing SVG files, depending on the query parameters used by the library and the version of sku they are using. Ensure changes made to libraries for the purpose of Vite compatibility are communicated clearly in the release notes.