PackShip v2 is now available! 🚀 Learn more

Webpack

Webpack is a powerful bundler that PackShip uses to compile and package your code. This guide explains the webpack configuration used in PackShip projects and how to customize it for your needs.

Default Configuration

When you initialize a new project with PackShip, it generates a default webpack configuration that works well for most React component libraries. Here's what it looks like:

// webpack.config.js
import path from 'path';

export default {
  mode: 'production',
  entry: './src/index.tsx',
  output: {
    path: path.resolve('dist'),
    filename: 'index.js',
    library: {
      type: 'umd',
      name: 'MyPackage',
    },
    globalObject: 'this',
    publicPath: '/',
  },
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
            },
          },
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-react', '@babel/preset-env'],
            },
          },
        ],
      },
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[name][ext]',
        },
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  externals: {
    react: 'react',
    'react-dom': 'react-dom',
  },
};

Key Configuration Options

Entry Point

The entry option specifies the entry point of your package. By default, it's set to ./src/index.tsx.

entry: './src/index.tsx',

Output

The output option configures how webpack emits the bundled files:

  • path: The directory where the bundled files will be placed (default: dist)
  • filename: The name of the output file (default: index.js)
  • library: Configures how the library is exposed, using UMD format by default
  • globalObject: Determines the global object reference, set to this for compatibility
  • publicPath: The public URL of the output directory when referenced in a browser

Module Rules

The module.rules array defines how different file types should be processed:

  • TypeScript/TSX files are processed with ts-loader (with transpileOnly for faster builds) and babel-loader
  • JavaScript/JSX files are processed with babel-loader
  • Images and SVGs are handled as assets with customizable output paths
  • CSS files are processed with style-loader and css-loader

Resolve

The resolve.extensions array specifies which file extensions webpack should resolve automatically. This allows you to import modules without specifying their extensions.

resolve: {
  extensions: ['.js', '.jsx', '.ts', '.tsx'],
},

Externals

The externals option specifies dependencies that should not be bundled with your package. By default, React and ReactDOM are marked as external, which means they won't be included in your bundle. This is important for React component libraries to avoid bundling React twice.

externals: {
  react: 'react',
  'react-dom': 'react-dom',
},

Marking dependencies as external reduces the size of your bundle and avoids potential issues with duplicate dependencies.

Customizing Webpack Configuration

You can customize the webpack configuration to suit your specific needs. Here are some common customizations:

Adding Support for SASS/SCSS

To add support for SASS/SCSS files, you need to install the required loaders:

npm install --save-dev sass sass-loader

Then update your webpack configuration:

// webpack.config.js
export default {
  // ... other config
  module: {
    rules: [
      // ... other rules
      {
        test: /\.s[ac]ss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ],
  },
};

Adding Support for PostCSS

To use PostCSS for advanced CSS processing, install the required packages:

npm install --save-dev postcss postcss-loader autoprefixer postcss-preset-env

Create a postcss.config.js file:

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer'),
    require('postcss-preset-env')({
      stage: 3,
      features: {
        'nesting-rules': true,
      },
    }),
  ],
};

Then update your webpack configuration:

// webpack.config.js
export default {
  // ... other config
  module: {
    rules: [
      // ... other rules
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
        ],
      },
    ],
  },
};

Multiple Output Formats

To build your package for multiple formats (CommonJS, ESM, and UMD), you can create separate webpack configurations:

// webpack.config.js
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Base configuration
const baseConfig = {
  mode: 'production',
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  module: {
    rules: [
      // ... rules as before
    ],
  },
  externals: {
    react: 'react',
    'react-dom': 'react-dom',
  },
};

// UMD build
const umdConfig = {
  ...baseConfig,
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist/umd'),
    filename: 'index.js',
    library: {
      type: 'umd',
      name: 'MyPackage',
    },
    globalObject: 'this',
  },
};

// CommonJS build
const cjsConfig = {
  ...baseConfig,
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist/cjs'),
    filename: 'index.js',
    library: {
      type: 'commonjs2',
    },
  },
};

// ESM build
const esmConfig = {
  ...baseConfig,
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist/esm'),
    filename: 'index.js',
    library: {
      type: 'module',
    },
  },
  experiments: {
    outputModule: true,
  },
};

export default [umdConfig, cjsConfig, esmConfig];

Then update your package.json to specify the different entry points:

{
  "name": "my-package",
  "version": "1.0.0",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "unpkg": "dist/umd/index.js",
  "types": "dist/types/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    }
  },
  // ... other package.json fields
}

Development Mode

For development, you might want to use a different webpack configuration. You can create a separate configuration file or use environment variables to switch between development and production modes.

// webpack.config.js
import path from 'path';

export default (env) => {
  const isDevelopment = env.development === true;
  
  return {
    mode: isDevelopment ? 'development' : 'production',
    entry: './src/index.tsx',
    output: {
      path: path.resolve('dist'),
      filename: 'index.js',
      library: {
        type: 'umd',
        name: 'MyPackage',
      },
      globalObject: 'this',
      publicPath: '/',
    },
    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    module: {
      rules: [
        // ... module rules
      ],
    },
    externals: {
      react: 'react',
      'react-dom': 'react-dom',
    },
    devtool: isDevelopment ? 'inline-source-map' : 'source-map',
    ...(isDevelopment && {
      devServer: {
        static: './dist',
        hot: true,
        open: true,
        port: 9000,
      },
    }),
  };
};

You can then run webpack in development mode with:

npm run build -- --env development

Adding a Development Server

To use webpack-dev-server for development, install the required package:

npm install --save-dev webpack-dev-server

Add a script to your package.json:

{
  "scripts": {
    "start": "webpack serve --env development",
    "build": "webpack --env production"
  }
}

Using webpack-dev-server provides features like hot module replacement (HMR), which allows you to see changes in real-time without refreshing the page.