Lesson 03-Build and Packaging

Webpack

Introduction: Webpack is a powerful module bundler that supports loaders and plugins to process various types of resources, such as JS, CSS, images, etc. It can bundle modular code and resources into one or more bundles for easy management and loading.

Basic Usage:

Installation: First, ensure you have installed Node.js and npm. Then, in the project root directory, run the following command to install Webpack:

npm install webpack webpack-cli --save-dev

Configuration: Create a webpack.config.js file to configure Webpack. A simple configuration is as follows:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // Used to automatically generate HTML files
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Used to extract CSS into separate files
const TerserPlugin = require('terser-webpack-plugin'); // Used to compress JS
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // Used to compress CSS
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // Cleans the output directory

module.exports = {
  // Entry point
  entry: './src/index.js',

  // Output configuration
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js', // Use content hash to improve caching efficiency
    publicPath: '/' // Root path for static resources
  },

  // Mode, affects code optimization level and source map generation
  mode: 'production', // or 'development'

  // Module resolution
  resolve: {
    extensions: ['.js', '.jsx', '.json'], // Automatically resolve these extensions
    alias: {
      '@': path.resolve(__dirname, 'src'), // Alias to simplify import paths
    },
  },

  // Module rules, define how to process different types of modules
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', // Use Babel to transpile ES6+
        },
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', // Process CSS
          'postcss-loader', // Apply PostCSS plugins, such as autoprefixing
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              outputPath: 'images', // Output directory for images
            },
          },
        ],
      },
    ],
  },

  // Plugin configuration
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html', // HTML template
      favicon: './public/favicon.ico', // Favicon
      minify: { // HTML minification options
        collapseWhitespace: true,
        removeComments: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css', // Extracted CSS filename
    }),
    new CleanWebpackPlugin(), // Clean the output directory
    // Plugins for compressing JS and CSS
    new TerserPlugin(),
    new OptimizeCSSAssetsPlugin(),
  ],

  // Development server configuration (only effective in development mode)
  devServer: {
    contentBase: './dist',
    hot: true, // Hot module replacement
    port: 3000, // Server port
  },

  // Performance hints to avoid generating overly large bundles
  performance: {
    hints: process.env.NODE_ENV === 'production' ? 'warning' : false,
    maxEntrypointSize: 500000,
    maxAssetSize: 300000,
  },
};

Bundling: Run the following command in the terminal to start bundling:

npx webpack

Rollup

Rollup is a lightweight JavaScript module bundler that focuses on efficiently bundling libraries or applications, especially those using ES modules.

Installing Rollup

First, ensure you have installed Node.js and npm. Then, in the project root directory, install Rollup and its necessary plugins via npm:

npm init -y  # Initialize the project and generate a package.json file (if not already present)
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs @rollup/plugin-babel @rollup/plugin-json --save-dev

The following commonly used plugins are installed:

  • rollup-plugin-node-resolve: Helps Rollup find external modules.
  • rollup-plugin-commonjs: Converts CommonJS modules to ES modules.
  • @rollup/plugin-babel: Uses Babel to transpile code, supporting the latest JavaScript features.
  • @rollup/plugin-json: Allows importing JSON files.

Creating a Rollup Configuration File

Next, create a rollup.config.js file in the project root directory and configure Rollup:

// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser'; // Used for code minification in production

export default {
  input: 'src/index.js', // Entry file
  output: [
    {
      file: 'dist/bundle.cjs', // CommonJS output
      format: 'cjs',
    },
    {
      file: 'dist/bundle.esm.js', // ES module output
      format: 'es',
    },
    {
      file: 'dist/bundle.min.js', // UMD format for browsers, minified
      format: 'umd',
      name: 'MyLibrary', // Global variable name required for UMD format
      plugins: [terser()], // Minify code for production
    },
  ],
  plugins: [
    resolve(), // Resolve external modules
    commonjs(), // Convert CommonJS modules
    babel({
      exclude: 'node_modules/**', // Exclude files in node_modules
      babelHelpers: 'bundled',
      presets: ['@babel/preset-env'],
    }),
    json(), // Support JSON file imports
  ],
  external: ['lodash'], // Specify external dependencies that won’t be bundled
};

Executing Rollup Bundling

Once configured, you can run Rollup to bundle your code with the following command:

npx rollup -c  # Bundle using the configuration file

This command will bundle your code based on the configuration in rollup.config.js, generating output files in the specified formats.

Notes

  • Depending on your project’s needs, you may need to install additional plugins to handle specific resource types, such as CSS, images, etc.
  • The configuration for @rollup/plugin-babel should be adjusted based on your project requirements, such as adding TypeScript support.
  • To streamline development, you can add the bundling command to the scripts section of package.json, e.g., "build": "rollup -c".

Parcel

Parcel is a fast, zero-configuration web application bundler designed to simplify the frontend development process. It automatically handles the bundling, optimization, and compression of JavaScript, CSS, HTML, images, and other resources. A key feature of Parcel is its out-of-the-box usability, requiring no complex configuration, making it ideal for rapid prototyping or small to medium-sized projects.

Installing Parcel

First, ensure you have installed Node.js and npm. Then, in the project root directory, install Parcel via npm:

npm init -y  # Initialize the project and generate a package.json file (if not already present)
npm install parcel-bundler --save-dev  # Install Parcel as a development dependency

Using Parcel

Parcel is very simple to use:

  1. Project Structure: Create a src folder in the project root and place your main entry file, typically index.html or main.js, inside it.
  2. Start the Development Server: In the terminal, navigate to the project root and run the following command to start the development server:
npx parcel serve src/index.html

This will automatically watch for file changes and open your application in the browser. Parcel handles bundling, hot module replacement (HMR), and more.

Complete Configuration (Although Parcel promotes zero configuration, limited configuration can be done via .parcelrc or environment variables)
While Parcel emphasizes zero configuration, you may sometimes need to tweak certain behaviors. This can be done using a .parcelrc file or environment variables.

.parcelrc Example

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.svg": ["@parcel/transformer-svg-react"]
  },
  "resolvers": [
    ...
  ],
  "optimizers": {
    "*.png": ["@parcel/optimizer-png"]
  },
  "packagers": {
    "*.json": ["@parcel/packager-json"]
  }
}

In this example, we specify a transformer to convert .svg files into React components and configure an optimizer to optimize .png images.

Environment Variable Configuration
You can also control Parcel’s behavior through environment variables, for example:

  • PARCEL_PUBLIC_URL=/my-app sets the public path.
  • NODE_ENV=production sets the production environment during builds.

Building for Production

When you’re ready to deploy your application, run the following command to build the production version:

npx parcel build src/index.html

This will generate optimized production build files in the dist directory.

Gulp

Gulp is a stream-based automation build tool that allows developers to define a series of tasks through simple code to automate common frontend development tasks, such as compilation, compression, and file merging.

Installing Gulp

First, ensure you have Node.js and npm installed. Then, in the project root directory, install Gulp and its CLI tool via npm:

npm init -y  # Initialize the project and generate a package.json file (if not already present)
npm install gulp gulp-cli --save-dev  # Install Gulp and its CLI as development dependencies

Next, install some commonly used Gulp plugins to handle specific tasks, such as compiling Sass, compressing CSS, and JavaScript:

npm install gulp-sass gulp-clean-css gulp-uglify-es --save-dev

Creating a Gulp Configuration File

In the project root directory, create a file named gulpfile.js, which is Gulp’s configuration file where all tasks are defined:

// Import Gulp
const gulp = require('gulp');

// Import plugins
const sass = require('gulp-sass')(require('sass')); // Compile Sass
const cleanCSS = require('gulp-clean-css'); // Compress CSS
const uglify = require('gulp-uglify-es').default; // Compress JavaScript

// Define tasks
function styles() {
  return gulp.src('src/scss/**/*.scss') // Source file path
    .pipe(sass()) // Compile Sass
    .pipe(cleanCSS({ compatibility: 'ie8' })) // Compress CSS
    .pipe(gulp.dest('dist/css')); // Output directory
}

function scripts() {
  return gulp.src('src/js/**/*.js') // Source file path
    .pipe(uglify()) // Compress JavaScript
    .pipe(gulp.dest('dist/js')); // Output directory
}

// Define default task, can run multiple tasks simultaneously
const build = gulp.parallel(styles, scripts);

// Export tasks to be called by the Gulp CLI
exports.default = build;

Running Gulp Tasks

Once the configuration is ready, you can run Gulp tasks from the terminal with the following command:

npx gulp  # If gulp-cli is installed globally, you can run gulp directly

This command executes the default task defined in gulpfile.js (in this case, the build task), which compiles and compresses Sass files and compresses JavaScript files in sequence.

Notes

  • Gulp 4 introduced a new task declaration approach, using gulp.series and gulp.parallel to combine tasks. Ensure you are using Gulp 4.
  • You can install additional Gulp plugins based on project needs to handle images, fonts, HTML, and other resources.
  • Gulp’s strength lies in its high customizability, allowing you to create any number of tasks and chain them together via pipes to form complex build workflows.

Grunt

Grunt is a Node.js-based task runner that automates common build tasks, such as compilation, testing, merging, and code compression, through a simple configuration file.

Installing Grunt

  1. Install Node.js: Ensure Node.js is installed on your system. Download and install the latest version from https://nodejs.org/.
  2. Initialize the Project: In the project root directory, open a terminal and run the following command to initialize a new npm project:
npm init -y
  1. Install Grunt CLI Globally: Grunt’s command-line interface needs to be installed globally once:
npm install -g grunt-cli
  1. Install Grunt Locally in the Project: In the project root directory, run the following command:
npm install grunt --save-dev
  1. Install Grunt Plugins: Install the appropriate Grunt plugins based on your needs. For example, install grunt-contrib-uglify to compress JavaScript files:
npm install grunt-contrib-uglify --save-dev

Creating a Grunt Configuration File

In the project root directory, create a file named Gruntfile.js (note the capital G), which is Grunt’s configuration file.

module.exports = function(grunt) {
  // Load Grunt plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Configure tasks
  grunt.initConfig({
    uglify: {
      options: {
        mangle: true, // Mangle variable names
        compress: true, // Compress code
        preserveComments: 'some' // Preserve license comments
      },
      my_target: {
        files: {
          'dist/main.min.js': ['src/**/*.js'] // Mapping of source and target files
        }
      }
    }
  });

  // Register tasks
  grunt.registerTask('default', ['uglify']); // Define the default task
};

Running Grunt Tasks

Once the configuration is ready, you can run Grunt tasks from the terminal with the following command:

grunt  # Run the default task

Or run a specific task:

grunt uglify  # Run the uglify task

Notes

  • Grunt configuration is based on JavaScript objects, and you can add more tasks and configuration options as needed.
  • Grunt has a wide range of plugins covering everything from code compression, compilation, and testing to deployment. Choose plugins based on project needs.
  • The Gruntfile.js configuration is highly flexible, allowing you to define multiple tasks, task dependencies, custom tasks, and more.
  • Although Grunt was once very popular, its usage has declined with the rise of modern build tools like Webpack and Rollup. However, for specific scenarios or maintaining existing projects, Grunt remains a reliable choice.

Vite

Vite is a modern frontend build tool developed by Vue.js creator Evan You. It leverages the browser’s native ES module imports to provide fast cold startup speeds and an efficient development server experience.

Installing Vite

First, ensure you have installed Node.js and npm. Then, in the project root directory, initialize a project and install Vite via npm:

npm init vite@latest

This command will guide you through creating a new Vite project, allowing you to choose a template (e.g., Vue, React, vanilla JavaScript) and automatically set up the project structure and dependencies.

Using Vite

Once the project is created, you can start the development server with the following command:

npm run dev

This will launch a hot-reloading development server, and you can see your application in the browser, with any changes reflected almost instantly.

Vite Configuration File

Vite uses vite.config.js as its configuration file, located in the project root. Here is a basic configuration example:

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  // Application entry
  root: './',
  publicDir: 'public',
  base: './',
  server: {
    host: '0.0.0.0', // Allow external access
    port: 3000, // Server port
    open: true, // Automatically open the browser
  },
  build: {
    outDir: 'dist', // Build output directory
    assetsDir: 'assets', // Static assets directory
    target: 'es2015', // Set compatibility target browser version
    minify: 'terser', // Minify code
    sourcemap: true, // Generate source maps
  },
  // Configure aliases
  resolve: {
    alias: {
      '@': '/src',
    },
  },
  // Plugin configuration
  plugins: [],
});

Configuration Explanation

  • root: Project root directory.
  • publicDir: Static assets directory, defaults to public.
  • base: Base URL path for deploying the application.
  • server: Development server configuration.
  • build: Build options, including output directory, static assets directory, compatibility target, code minification, etc.
  • resolve.alias: Configure path aliases for easier module imports.
  • plugins: List of plugins to extend Vite’s functionality.

Running a Build

When ready to deploy the application, run the following command to build the production version:

npm run build

This will generate production-ready static files in the specified outDir directory.

Notes

  • Vite’s configuration options are highly flexible and can be adjusted based on project needs.
  • Vite supports multiple frameworks like Vue, React, Preact, and Svelte, with a rich plugin ecosystem to meet various development needs.
  • Vite’s development server offers advanced features like hot module replacement, on-demand compilation, and ESM service worker support, all available without extra configuration.

Snowpack

Snowpack is a fast frontend build tool that leverages the browser’s native ES module imports to provide a near-instant development experience. Snowpack serves directly from source code during development and only bundles and optimizes during production builds.

Installing Snowpack

First, ensure you have installed Node.js and npm. Then, create a new project directory and initialize npm:

mkdir my-snowpack-project
cd my-snowpack-project
npm init -y

Next, install Snowpack:

npm install snowpack --save-dev

Initializing Snowpack Configuration

Snowpack automatically generates a snowpack.config.js configuration file in the project root. If it doesn’t, you can create one manually. Here is a basic configuration example:

// snowpack.config.js
module.exports = {
  // Entry points
  entryPoints: ["src/index.js"],

  // Output directory
  outDir: "build",

  // Preprocessor configuration, e.g., TypeScript, SASS, etc.
  preprocess: {
    // Add TypeScript preprocessor
    typescript: {},
  },

  // Build options
  buildOptions: {
    // Generate source maps
    sourcemap: true,

    // Static assets directory
    publicDir: "public",
  },

  // Plugin list
  plugins: [],

  // Additional package alias configuration
  aliases: {
    components: "./src/components",
  },

  // MIME type mappings
  mimeTypes: {
    ".ts": "text/typescript",
  },
};

Development Server

Start the Snowpack development server:

npx snowpack dev

This will launch a development server that watches for file changes and automatically refreshes the browser.

Building for Production

Build production-ready static files:

npx snowpack build

This will compile the source code and output it to the build directory based on the configuration settings.

Notes

  • Snowpack supports native ES module imports, making it well-suited for modern frontend frameworks and libraries like React, Vue, and Preact.
  • Snowpack supports additional functionality through its plugin system, such as type checking, style preprocessors, and automatic polyfill imports.
  • Since Snowpack serves directly from source code, you may need to import .jsx, .ts, or other source files directly in the browser during development, rather than traditional .js output files.

Esbuild

Esbuild is a fast JavaScript bundler known for its remarkable build speed and low memory usage. Esbuild supports native ES6 modules, TypeScript, JSX, and Tree Shaking, and provides both a command-line tool and an API.

Installing Esbuild

First, ensure you have installed Node.js. Then, install esbuild via npm:

npm install esbuild --save-dev

Basic Usage

Via Command Line
Esbuild provides a command-line interface (CLI) for bundling files directly:

npx esbuild entry.js --bundle --outfile=bundle.js
  • entry.js is your entry file.
  • --bundle indicates that all dependencies should be bundled into a single file.
  • --outfile=bundle.js specifies the output file name.

Via JavaScript API
Esbuild also supports more flexible configuration through its JavaScript API:

const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/bundle.js',
  bundle: true,
  minify: true, // Minify code
  sourcemap: true, // Generate source maps
  loader: {
    '.js': 'jsx', // Treat .js files as JSX
  },
}).catch(() => process.exit(1));

Advanced Configuration

Esbuild’s configuration is highly flexible, allowing fine-grained control by passing options to the build method. Here is an example with more configuration:

const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/bundle.js',
  bundle: true,
  minify: process.env.NODE_ENV === 'production', // Minify based on environment variable
  sourcemap: true,
  target: 'es2015', // Set target browser version
  format: 'iife', // Output in Immediately Invoked Function Expression format
  define: {
    'process.env.NODE_ENV': `"${process.env.NODE_ENV}"`, // Define environment variables
  },
  plugins: [
    // Custom plugin example
    {
      name: 'my-plugin',
      setup(build) {
        build.onResolve({ filter: /^react$/ }, args => {
          return { path: args.path, external: true };
        });
      },
    },
  ],
}).catch(() => process.exit(1));

Notes

  • Esbuild’s speed advantage comes from its core being written in Go and provided to Node.js via WASM.
  • While Esbuild is powerful, it is relatively young, and its ecosystem is smaller compared to Webpack or Rollup, potentially lacking support for certain specific plugins or features.
  • Esbuild does not natively handle CSS, images, or other resources, but these can be integrated through its plugin system using third-party tools.

Share your love