Lesson 02-Package Management, Bundling Tools, and Performance Monitoring

NPM

Common Configuration File

{
  "name": "your-package-name",
  "version": "1.0.0",
  "description": "A short description of your package",
  "author": "Your Name <you@example.com>",
  "license": "MIT",
  "private": false, // or "true" if your package is private

  "main": "dist/index.js", // points to the main entry file of the project

  "scripts": {
    "start": "node dist/index.js",
    "build": "webpack --config webpack.config.js",
    "test": "jest",
    "lint": "eslint .",
    "prepublishOnly": "npm run build" // execute build before publishing
  },

  "dependencies": {
    "express": "^4.17.1",
    "react": "^17.0.2"
  },

  "devDependencies": {
    "webpack": "^5.58.1",
    "webpack-cli": "^4.9.1",
    "babel-loader": "^8.2.3",
    "eslint": "^7.32.0",
    "jest": "^27.3.1",
    "babel-jest": "^27.3.1",
    "@babel/core": "^7.15.5",
    "@babel/preset-env": "^7.15.6",
    "@babel/preset-react": "^7.14.5"
  },

  "peerDependencies": {
    "react": "^16.8.0 || ^17.0.0",
    "react-dom": "^16.8.0 || ^17.0.0"
  },

  "engines": {
    "node": ">=14.0.0",
    "npm": ">=6.0.0"
  },

  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ],

  "repository": {
    "type": "git",
    "url": "https://github.com/yourusername/your-repo.git"
  },

  "keywords": [
    "example",
    "npm",
    "package"
  ]
}

Common Properties

Basic Properties

  • name: The name of the package, which must be unique and follow npm naming conventions.
  • version: The version number of the package, adhering to Semantic Versioning (SemVer) rules.
  • description: A brief description of the package.
  • author: Information about the package’s author, which can be a string or object containing the author’s name, email, etc.
  • license: The type of license for the package, such as “MIT” or “Apache-2.0”.

private Property

  • private: When set to true, indicates that the package is private and will not be published to the public npm registry.

main Property

  • main: Specifies the path to the project’s entry file, which npm uses as the module’s entry point. For example, "main": "index.js".

scripts Property

  • scripts: Defines a series of script commands for common tasks such as building, testing, and starting the project. For example:
  "scripts": {
    "start": "node index.js",
    "test": "jest",
    "build": "webpack"
  }

dependencies Property

  • dependencies: Lists the npm packages and their versions that the project directly depends on in production. For example:
  "dependencies": {
    "express": "^4.17.1",
    "react": "^17.0.2"
  }

devDependencies Property

  • devDependencies: Lists the dependencies required only in the development environment, such as testing frameworks and build tools, which end users do not need. For example:
  "devDependencies": {
    "eslint": "^7.32.0",
    "jest": "^27.3.1",
    "webpack": "^5.58.1"
  }

peerDependencies Property

  • peerDependencies: Specifies the version range of external packages that the current package depends on but does not directly manage. Users are expected to install compatible versions themselves. This is commonly used for plugins or libraries to avoid version conflicts. For example:
  "peerDependencies": {
    "react": "^16.8.0 || ^17.0.0",
    "react-dom": "^16.8.0 || ^17.0.0"
  }

Dependency Version Management

Exact Version:

  • Specifies a specific version, e.g., "express": "4.17.1", ensuring the exact version 4.17.1 is installed every time.

Minor Version Range (tilde operator):

  • Uses the tilde (~), e.g., "express": "~4.17.1", which installs the latest 4.17.x version but not beyond 4.17.0 (i.e., excludes 4.18.0 or higher).

Major Version Range (caret operator):

  • Uses the caret (^), e.g., "express": "^4.17.1", which installs the latest 4.x.x version but not beyond 4.0.0 (i.e., excludes 5.0.0 or higher).

Latest Version:

  • Omits a version number, e.g., "express": "latest", which always installs the latest version from the npm registry. Not recommended for production due to potential breaking changes.

Lock File:

  • The package-lock.json or yarn.lock file records the exact versions installed, ensuring consistent results across installations even if dependency ranges change.

npm Update Dependencies:

  • Use npm update express to update express to the latest version within the specified range.
  • Use npm outdated to check for available updates for dependencies.

npm Upgrade All Dependencies:

  • npm update updates all installed dependencies to the latest versions within their specified ranges.
  • npm install --save-dev --save-exact updates all dev dependencies to their exact latest versions.

npm Install Specific Version:

  • npm install express@4.17.1 installs the specified version 4.17.1.

npm Remove Dependency:

  • npm uninstall express removes the express dependency from the project.

npm install Mechanism

The npm install command is one of the most commonly used commands in the Node.js ecosystem for installing project dependencies. Its mechanism can be summarized as follows:

1. Read Configuration File:

  • npm first looks for the package.json file in the project root, which lists all dependencies and their version ranges.
  • It also considers the .npmrc configuration file, which can be set at the project, user, or global level to customize npm behavior, such as specifying the registry source or access tokens.

2. Resolve Dependencies:

  • If a package-lock.json file exists, npm prioritizes it to determine the exact versions of dependencies, ensuring consistency and reproducibility.
  • If no package-lock.json exists, npm resolves dependencies based on the version ranges in package.json and builds a dependency tree to determine specific versions.

3. Download Dependencies:

  • Based on the resolved dependency list, npm downloads the corresponding module tarballs from the configured registry (default: https://registry.npmjs.org).
  • Downloaded packages are cached in the local .npm directory for reuse in subsequent installations, reducing network requests.

4. Install Dependencies:

  • The downloaded tarballs are extracted to the project’s node_modules directory, which contains direct and indirect dependencies (dependencies of dependencies) in a flattened structure.
  • During installation, npm executes lifecycle scripts for each module, such as preinstall, install, and postinstall, which can perform custom actions like compiling source code or creating symlinks.

5. Generate or Update package-lock.json:

  • After installation, npm generates or updates the package-lock.json file to record the exact versions and dependency relationships installed, ensuring consistent results across environments.

6. Handle Optional Dependencies:

  • npm supports optionalDependencies and devDependencies. The former are optional production dependencies, and the latter are development dependencies, handled differently depending on the environment (production or development).

In summary, npm install focuses on resolving, downloading, installing dependencies, and ensuring consistency and reproducibility.

Publishing Your Own npm Package

1. Create an npm Account:
If you don’t have an npm account, register one at https://www.npmjs.com/ and follow the instructions.

2. Initialize the Project:
Ensure your project directory has a package.json file. If not, run the following command to create one:

npm init

This will guide you through entering basic project information such as name, version, description, author, and entry file.

3. Prepare Your Code:

  • Ensure your code follows good programming practices.
  • If your package is a JavaScript module, verify that the entry file (typically specified in the main field) is correct.
  • Write a README.md file with usage instructions and examples.
  • Add a .gitignore file to exclude unnecessary files or directories, such as node_modules.

4. Test Your Package:
Before publishing, ensure your package works in different environments. Use unit tests, integration tests, or other methods, including testing frameworks and tools listed in devDependencies.

5. Version Control:
Follow Semantic Versioning (SemVer) principles to set an appropriate version number for your package. Update the version field in package.json before each release.

6. Log in to npm:
Log in to your npm account from the command line:

npm login

Enter your username, password, and email address.

7. Publish the Package:
Once everything is ready, run the following command to publish your package:

npm publish

Note: Once published, you cannot modify the version number or content. Fixes or new features require publishing a new version.

8. Post-Publication Checks:
After publishing, verify your package on the npm website. Ensure the documentation and code are up-to-date and that users can install and use it smoothly.

Notes:

  • Ensure your package name is unique on npm using npm search <your-package-name>.
  • Before publishing, clean or ignore the node_modules directory to avoid accidental uploads.
  • For open-source packages, include a license, typically declared in a LICENSE file.
  • Use a .npmignore file to exclude files that shouldn’t be uploaded, such as test files or sensitive configurations.

Yarn

Yarn is a fast, reliable, and secure dependency management tool similar to npm but with additional features and performance optimizations. Yarn’s configuration is primarily done through .yarnrc.yml or .yarnrc files (newer versions recommend .yarnrc.yml).

Yarn Configuration File

# Yarn configuration file example

# Global cache directory for Yarn
cache-folder: ~/.yarn/cache

# Default registry address; can be changed to use a custom npm source
registry: "https://registry.yarnpkg.com"

# Enable Plug'n'Play (PnP) mode for faster dependency management
enable-pnp: true

# Automatically clean old dependency binaries during installation
auto-clean-binaries: true

# Default workspace protocol for managing Yarn workspaces
workspace-protocol: "npm:"

# Timeout setting in milliseconds
network-timeout: 60000

# Allow installation of unverified packages
offline-mirror-enable: false

# Path to offline mirror when offline-mirror-enable is true
offline-mirror: ""

# Shell for running scripts, defaults to bash but can be adjusted
script-shell: bash

# Custom environment variables for script execution
env:
  NODE_ENV: production
  MY_CUSTOM_VARIABLE: value

# Additional parameters for installation, e.g., proxy settings
install:
  extra-args: "--network-concurrency 1"

# Mirror settings to replace the default registry
#mirror:
#  "https://registry.yarnpkg.com":
#    "{protocol}://my.custom.registry/{path}"

# Configure workspace paths for Yarn workspaces
#workspaces:
#  - packages/**

Common Properties

  • cache-folder: Sets the location of Yarn’s cache directory.
  • registry: Specifies the registry address for dependency packages, allowing custom npm mirror sources.
  • enable-pnp: Enables or disables Plug’n’Play mode, which significantly improves dependency installation and project startup speed.
  • auto-clean-binaries: Determines whether Yarn automatically cleans old binary versions during dependency installation.
  • workspace-protocol: Specifies the protocol for workspace recognition and management in Yarn workspaces.
  • network-timeout: Sets the timeout for network requests.
  • offline-mirror-enable and offline-mirror: Control whether to use an offline mirror and specify its path.
  • script-shell: Specifies the shell used for executing scripts.
  • env: Defines custom environment variables injected during script execution.
  • install.extra-args: Passes additional command-line arguments to Yarn during installation.
  • mirror: Configures mirror sources to override the default registry address.
  • workspaces: Configures the locations of sub-packages or workspaces for projects using Yarn workspaces.

Version Management

Yarn provides several key features to manage dependency versions, ensuring project consistency and reproducibility:

  1. Lock File: Yarn uses a yarn.lock file to lock the exact versions of each dependency, including their dependencies. This ensures that running yarn install in any environment installs the exact versions specified in yarn.lock.
  2. Version Resolution Algorithm: Yarn uses a “leftmost match” algorithm to select dependency versions, choosing the smallest version number that satisfies all requirements to minimize incompatibility issues.
  3. Upgrade Dependencies: Use yarn upgrade or yarn upgrade [package] to upgrade individual or all dependencies to the latest compatible versions, automatically updating the yarn.lock file.
  4. Version Ranges: Yarn supports version range definitions similar to npm, such as ^, ~, *, and x.x.x, allowing flexible specification of dependency versions.

Yarn Operating Mechanism

Yarn’s operation involves the following core steps:

1. Resolve Dependencies:

  • When yarn install is executed, Yarn reads the package.json file to identify direct dependencies.
  • It then recursively parses the package.json files of these dependencies to identify all indirect dependencies, building a complete dependency tree.

2. Dependency Locking and Caching:

  • Yarn checks or creates a yarn.lock file to determine the exact versions of dependencies. If a yarn.lock file exists, Yarn strictly adheres to its versions; otherwise, it selects appropriate versions based on ranges and generates yarn.lock.
  • Yarn uses a local cache (located at ~/.yarn/cache) to store downloaded dependency packages, speeding up subsequent installations.

3. Concurrent Download and Installation:

  • To improve efficiency, Yarn downloads dependency packages in parallel, significantly reducing installation time.
  • After downloading, Yarn installs dependencies into the node_modules directory based on the yarn.lock file and manages dependency relationships.

4. Plug’n’Play (PnP) (Optional):

  • PnP is an alternative to the traditional node_modules structure, eliminating the node_modules directory and using a mapping file (.pnp.js) to indicate package locations. This speeds up installation and reduces disk usage.

5. Lifecycle Scripts:

  • During installation, Yarn executes lifecycle scripts defined by each package, such as preinstall, install, and postinstall, allowing custom operations.

Publishing and Updating Yarn Packages

Publishing a Yarn Package

1. Initialize the Project:

  • Run yarn init in your project root to create a package.json file.

2. Write Code:

  • Develop your module code, ensuring it follows npm’s module system, typically including one or more entry files.

3. Set Version:

  • In package.json, set the version field to the desired version, following Semantic Versioning (SemVer) rules.

4. Add Metadata:

  • Include necessary metadata in package.json, such as name, description, author, license, and repository.

5. Write Documentation:

  • Create a README.md file with detailed information and usage instructions for your package.

6. Test:

  • Write test cases and ensure your package passes all tests.

7. Log in to npm:

  • Run npm login or yarn login in the terminal to log in with your npm account.

8. Publish:

  • Run npm publish or yarn publish to publish your package to the npm registry. For the first publish, choose an available package name. If the name is not taken, it will be reserved and published.

Updating a Yarn Package

1. Update Code:

  • Make necessary changes to your package, such as fixing bugs or adding features.

2. Increment Version Number:

  • Update the version field in package.json based on SemVer rules, depending on the changes made.

3. Test:

  • Ensure the updated code passes all tests.

4. Publish Update:

  • Run npm publish or yarn publish to release the updated package to the npm registry. npm will verify your permissions and version number before publishing the new version.

5. Update yarn.lock:

  • If your package’s dependencies have updates, you may need to update the yarn.lock file to reflect these changes. Running yarn or yarn install will automatically update it.

6. Notify Users:

  • After releasing a new version, update your package’s documentation to inform users about changes and improvements.

Webpack

Introduction: Webpack is a powerful module bundler that supports loaders and plugins to process various resources, such as JavaScript, CSS, and images. It bundles modular code and resources into one or more bundles for efficient management and loading.

Basic Usage:

Installation: Ensure Node.js and npm are installed. Then, run the following command in your project root 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'); // Automatically generates HTML files
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Extracts CSS into separate files
const TerserPlugin = require('terser-webpack-plugin'); // Compresses JavaScript
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // Compresses 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', // Uses content hash for better caching
    publicPath: '/' // Root path for static resources
  },

  // Mode, affects code optimization 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 for simplified imports
    },
  },

  // Module rules for processing different types of modules
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', // Uses Babel to transpile ES6+
        },
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', // Processes CSS
          'postcss-loader', // Applies PostCSS plugins, e.g., autoprefixer
        ],
      },
      {
        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(), // Cleans the output directory
    // Plugins for compressing JS and CSS
    new TerserPlugin(),
    new OptimizeCSSAssetsPlugin(),
  ],

  // Development server configuration (only for 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 to start bundling:

npx webpack

Rollup

Introduction: Rollup is a lightweight module bundler, ideal for bundling libraries or projects using ES modules. It emphasizes code readability and minimal output.

Basic Usage:

Installation:

npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-babel @babel/core @babel/preset-env --save-dev

Configuration: Create a rollup.config.js file with the following configuration:

import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';

export default {
  input: 'src/index.js', // Entry file
  output: {
    file: 'dist/bundle.js', // Output file
    format: 'iife' // Immediately Invoked Function Expression format, suitable for browsers
  },
  plugins: [
    resolve(), // Enables Rollup to find external modules
    commonjs(), // Converts CommonJS modules to ES modules
    babel({ presets: ['@babel/preset-env'] }) // Transpiles ES6+ syntax with Babel
  ]
};

Bundling:

npx rollup -c

Parcel

Introduction: Parcel is a fast, zero-configuration web application bundler that works out of the box, automatically handling JavaScript, CSS, HTML, images, and other resources.

Installation:

npm install -D parcel-bundler

Configuration: Parcel requires minimal configuration. It automatically detects index.html or main.js as the entry file.

Bundling:

npx parcel build index.html

Performance Monitoring

1. Page Load Performance

  • Browser APIs: Use the Performance API (especially performance.timing and performance.navigation) to measure page load metrics such as First Contentful Paint (FCP), Largest Contentful Paint (LCP), Time to Interactive (TTI), and First Input Delay (FID).
  • Navigation Timing API: Provides timestamps for page load events, such as DNS lookup time, TCP connection time, and total page load time.
  • Resource Timing API: Records detailed timelines for each resource’s loading process, including request and response times.

2. JavaScript Execution Performance

  • Performance.measure(): Manually mark the start and end of code segments to measure the duration of specific operations.
  • Long Task API: Monitors long-running JavaScript tasks that may block the main thread, affecting page responsiveness.

3. Memory Monitoring

  • Chrome DevTools: Use the “Performance” tab in Chrome DevTools to record memory allocation, analyze heap snapshots, and detect memory leaks.
  • Memory API: Although less direct than CPU performance monitoring, the Web Memory API can be combined with periodic heap snapshots in DevTools to analyze object allocation and deallocation.

4. Impact of Third-Party Libraries and Packages

  • Bundle Analysis: Use tools like Webpack Bundle Analyzer to analyze the size of bundled files, identify large dependencies, and optimize by lazy-loading or finding alternatives.
  • Dynamic Imports (import()): Use dynamic imports to lazy-load modules, reducing initial page load time.

5. User Experience Monitoring

  • Real User Monitoring (RUM): Embed JavaScript snippets in production to collect real user performance data, using tools like PageSpeed Insights, Google Analytics, or Lighthouse.
  • Error Tracking: Integrate error tracking services like Sentry or Bugsnag to capture and report frontend errors, including JavaScript exceptions and resource loading failures.
Share your love