Lesson 08-Svelte Testing, Performance Optimization, and Bundling

Unit Testing and End-to-End Testing

Unit Testing

Unit testing focuses on testing individual components or functions to ensure they work as expected. For Svelte, you can use Jest, Mocha, or other testing frameworks to write unit tests.

Example: Unit Testing with Jest

Install Jest and Related Dependencies

npm install jest @testing-library/svelte @testing-library/jest-dom --save-dev

Configure Jest

Create or modify a jest.config.js file in the project root:

module.exports = {
  preset: 'svelte',
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.svelte$': ['svelte-jester', { preprocess: true }],
    '^.+\\.js$': 'babel-jest'
  },
  moduleNameMapper: {
    '^svelte/(.*)$': '<rootDir>/node_modules/svelte/$1'
  },
  setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect']
};

Write Unit Tests

Create a test file, e.g., MyComponent.test.js:

import { render, fireEvent } from '@testing-library/svelte';
import MyComponent from './MyComponent.svelte';

describe('MyComponent', () => {
  it('renders the correct text', () => {
    const { getByText } = render(MyComponent, { props: { message: 'Hello, World!' } });
    expect(getByText('Hello, World!')).toBeInTheDocument();
  });

  it('handles clicks correctly', async () => {
    const handleClick = jest.fn();
    const { getByRole } = render(MyComponent, { props: { onClick: handleClick } });
    const button = getByRole('button');
    await fireEvent.click(button);
    expect(handleClick).toHaveBeenCalled();
  });
});

Run Tests

Add a test script to package.json:

"scripts": {
  "test": "jest"
}

Then run:

npm test

End-to-End Testing

End-to-end (E2E) testing simulates user interactions with the application to ensure the entire workflow functions as expected. Cypress is a popular E2E testing framework for Svelte.

Example: End-to-End Testing with Cypress

Install Cypress

npm install cypress --save-dev

Write End-to-End Tests

Create a test file in the cypress/integration directory, e.g., myApp.spec.js:

describe('My Svelte App', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('displays the correct greeting', () => {
    cy.get('h1').should('contain', 'Welcome to My Svelte App');
  });

  it('handles form submission', () => {
    cy.get('#name-input').type('John Doe');
    cy.get('#submit-button').click();
    cy.get('.greeting').should('contain', 'Hello, John Doe!');
  });
});

Run End-to-End Tests

Run Cypress tests:

npx cypress open

Or run all tests:

npx cypress run

Test Coverage

Ensure your tests cover most of the codebase. Most testing frameworks, like Jest, support coverage reports:

npm test -- --coverage

Code Splitting and Lazy Loading

Code Splitting

Svelte’s build tools (e.g., Rollup) perform code splitting by default, separating components and modules into individual files. You can further customize this process to control which code is bundled together.

Rollup Plugin Example

// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';

const production = !process.env.ROLLUP_WATCH;

export default {
  input: 'src/main.js',
  output: {
    sourcemap: true,
    format: 'iife',
    name: 'app',
    file: 'public/build/bundle.js'
  },
  plugins: [
    svelte({
      dev: !production,
      css: css => {
        css.write('public/build/bundle.css');
      }
    }),
    resolve({
      browser: true,
      dedupe: ['svelte']
    }),
    commonjs(),
    !production && serve(),
    !production && livereload('public'),
    production && terser()
  ],
  watch: {
    clearScreen: false
  }
};

function serve() {
  let started = false;

  return {
    writeBundle() {
      if (!started) {
        started = true;
        require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
          stdio: ['ignore', 'inherit', 'inherit'],
          shell: true
        });
      }
    }
  };
}

Lazy Loading

Svelte supports dynamic imports (import()), enabling lazy loading. You can use dynamic imports in routes or components to implement lazy loading.

Svelte Router Example

// src/routes/[id].svelte
<script>
  let id;
  let Post;

  async function loadPost() {
    if (!Post) {
      const response = await fetch(`/api/posts/${id}`);
      const data = await response.json();
      Post = (await import(`../components/Post.svelte`)).default;
    }
    return new Post({ props: { post: data } });
  }

  $: postPromise = loadPost();
</script>

{#await postPromise}
  <p>Loading...</p>
{:then post}
  <main>
    <h1>{post.title}</h1>
    <p>{post.content}</p>
  </main>
{:catch error}
  <p>Error: {error.message}</p>
{/await}

Production Build and Optimization

Production Build

Use Svelte’s build command to prepare a production-ready build, which performs code splitting, minification, and other optimizations.

npm run build

Optimization Strategies

  • Minification and Compression: Use plugins like terser to minify JavaScript and CSS.
  • Long-Term Caching: Use content hashes and set HTTP cache headers.
  • Preloading and Prefetching: Use <link rel="preload"> and <link rel="prefetch"> to load critical resources early.
  • Tree Shaking: Svelte’s compiler automatically removes unused code.

Building Projects with Vite or Webpack

Building with Vite

Vite is a development server and build tool based on native browser ES modules, known for its fast startup and hot module replacement.

Installation and Initialization

# Install Vite and Svelte preset
npm install -D vite @sveltejs/vite-plugin-svelte

# Initialize Svelte project
vite init

Configure Vite

Configure the Svelte plugin in vite.config.js:

import { defineConfig } from 'vite';
import svelte from '@sveltejs/vite-plugin-svelte';

export default defineConfig({
  plugins: [svelte()],
});

Build the Project

# Development mode
npm run dev

# Production mode build
npm run build

Building with Webpack

Webpack is a mature module bundler with a rich ecosystem of plugins and loaders.

Install Dependencies

# Install Webpack and Svelte-related plugins
npm install -D webpack webpack-cli svelte-loader rollup-plugin-svelte

Configure Webpack

Create or edit webpack.config.js:

const path = require('path');
const svelte = require('svelte-loader');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'public'),
  },
  module: {
    rules: [
      {
        test: /\.svelte$/,
        use: {
          loader: 'svelte-loader',
          options: {
            emitCss: true,
            hotReload: true,
          },
        },
      },
      // Other rules for handling CSS, images, etc.
    ],
  },
  resolve: {
    extensions: ['.mjs', '.js', '.svelte'],
    mainFields: ['svelte', 'browser', 'module', 'main'],
  },
  devtool: 'source-map',
};

Build the Project

# Development mode
npx webpack --mode development

# Production mode build
npx webpack --mode production

Share your love