Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ reports/
storybook/
test/screenshots/
test/videos/
.webpack-cache/
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-order": "^6.0.4",
"tabbable": "^1.1.3",
"terser-webpack-plugin": "^5.3.14",
"ts-loader": "^6.2.1",
"typescript": "5.2.2",
"uuid": "^8.3.2",
Expand Down Expand Up @@ -358,5 +359,8 @@
},
"msw": {
"workerDirectory": [".storybook/public"]
},
"dependencies": {
"esbuild-loader": "^4.3.0"
}
}
201 changes: 180 additions & 21 deletions scripts/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const path = require('path');
const os = require('os');

const CircularDependencyPlugin = require('circular-dependency-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
const { BannerPlugin, DefinePlugin, IgnorePlugin } = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');

const license = require('./license');
const packageJson = require('../package.json');
Expand All @@ -18,10 +20,13 @@ const hasAllBrowserSupport = isProdBuild || process.env.BROWSERSLIST_ENV === 'pr
const hasBundleAnalysis = process.env.BUNDLE_ANALYSIS === 'true';
const hasReact = process.env.REACT === 'true';

const language = process.env.LANGUAGE;
const language = process.env.LANGUAGE || 'en-US';
const entryPoint = process.env.ENTRY;
const outputDir = process.env.OUTPUT;

// Optimize for number of CPU cores
const numCPUs = os.cpus().length;

const entries = {
explorer: path.resolve('src/elements/wrappers/ContentExplorer.js'),
openwith: path.resolve('src/elements/wrappers/ContentOpenWith.js'),
Expand All @@ -42,6 +47,10 @@ const getConfig = isReactBundle => {
filename: `[name]${isReactBundle ? '' : '.no.react'}.js`,
path: outputDir ? path.resolve(outputDir) : path.resolve('dist', version, language),
publicPath: `/${version}/${language}/`,
// Optimize chunk naming for better caching
chunkFilename: '[name].[contenthash].js',
// Clean output directory
clean: true,
},

resolve: {
Expand All @@ -51,14 +60,32 @@ const getConfig = isReactBundle => {
'box-ui-elements-locale-data': path.resolve(`i18n/${language}`),
'box-locale-data': path.resolve(`node_modules/@box/cldr-data/locale-data/${language}`),
},
// Cache resolved modules for faster builds
cacheWithContext: false,
},

resolveLoader: {
modules: [path.resolve('src'), path.resolve('node_modules')],
},

// Enable persistent caching for faster rebuilds
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
cacheDirectory: path.resolve('.webpack-cache'),
},

devServer: {
host: '0.0.0.0',
// Enable hot module replacement for faster development
hot: true,
// Optimize dev server performance
compress: true,
client: {
overlay: false,
},
},

module: {
Expand All @@ -70,23 +97,161 @@ const getConfig = isReactBundle => {
},
},
{
test: /\.(js|mjs|ts|tsx)$/,
// Exclude node_modules in development mode to improve build performance
test: /\.(ts|tsx)$/,
// Use esbuild-loader for TypeScript files
exclude: /node_modules/,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2020',
// Enable source maps for debugging
sourcemap: isDevBuild,
// Optimize for production builds
minify: isProdBuild,
// Enable tree shaking
treeShaking: true,
// Optimize for bundle size
keepNames: false,
},
},
],
},
{
test: /\.(js|mjs)$/,
// Use babel-loader for JavaScript files to handle modern syntax
exclude: hasAllBrowserSupport
? /@babel(?:\/|\\{1,2})runtime|pikaday|core-js/
: /node_modules\/(?!@box\/cldr-data)/, // Exclude node_modules except for @box/cldr-data which is needed for i18n
loader: 'babel-loader',
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
],
},
{
test: /\.s?css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
// Enable CSS modules if needed
modules: false,
// Optimize CSS processing
importLoaders: 2,
sourceMap: isDevBuild,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: isDevBuild,
postcssOptions: {
plugins: [
'autoprefixer',
// Add cssnano for production builds
...(isProdBuild ? ['cssnano'] : []),
],
},
},
},
{
loader: 'sass-loader',
options: {
sourceMap: isDevBuild,
// Optimize Sass compilation
sassOptions: {
outputStyle: isProdBuild ? 'compressed' : 'expanded',
},
},
},
],
},
],
},

// Optimize performance settings
performance: {
maxAssetSize: 2000000,
maxEntrypointSize: 2000000,
// Add hints for optimization
hints: isProdBuild ? 'warning' : false,
},

// Add optimization settings
optimization: {
// Enable tree shaking
usedExports: true,
sideEffects: false,
// Optimize module concatenation
concatenateModules: isProdBuild,
// Split chunks for better caching
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
},
},
},
// Optimize runtime chunk
runtimeChunk: 'single',
// Minimize in production
minimize: isProdBuild,
minimizer: [
// Use Terser for JavaScript minification
new TerserPlugin({
parallel: numCPUs,
terserOptions: {
compress: {
drop_console: isProdBuild,
drop_debugger: isProdBuild,
pure_funcs: isProdBuild ? ['console.log'] : [],
},
mangle: {
safari10: true,
},
format: {
comments: false,
},
},
extractComments: false,
}),
// Use CSS minimizer
new CssMinimizerPlugin({
parallel: numCPUs,
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
normalizeWhitespace: true,
minifyFontValues: true,
minifySelectors: true,
},
],
processorOptions: {
safe: true,
},
},
}),
],
},

plugins: [
Expand All @@ -100,22 +265,10 @@ const getConfig = isReactBundle => {
},
}),
new MiniCssExtractPlugin({
filename: '[name].css',
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
ignoreOrder: true,
}),
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
processorOptions: {
safe: true,
},
},
}),
new IgnorePlugin({
resourceRegExp: /moment$/, // Moment is optionally included by Pikaday, but is not needed in our bundle
}),
Expand All @@ -131,12 +284,18 @@ const getConfig = isReactBundle => {
hash: false,
timings: true,
version: false,
// Add more detailed stats for optimization
modules: false,
reasons: false,
warnings: true,
errors: true,
},

// Enable source maps for development
devtool: isDevBuild ? 'eval-source-map' : false,
};

if (isDevBuild) {
config.devtool = 'source-map';

config.plugins.push(
new CircularDependencyPlugin({
exclude: /node_modules/,
Expand Down
Loading