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 defaultglobalObject
: Determines the global object reference, set tothis
for compatibilitypublicPath
: 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) andbabel-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
andcss-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.