javascript

exercises

exercises.js⚔
// ╔══════════════════════════════════════════════════════════════════════════════╗
// ā•‘                    BUNDLERS & BUILD TOOLS - EXERCISES                         ā•‘
// ā•‘               Practice Configuration & Build Concepts                          ā•‘
// ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                              EXERCISE OVERVIEW                                   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                                  │
│  Exercise 1: Webpack Configuration - Create config from scratch                 │
│  Exercise 2: Vite Configuration - Setup a Vite project                          │
│  Exercise 3: Rollup Configuration - Configure for library build                 │
│  Exercise 4: Code Splitting - Implement lazy loading                            │
│  Exercise 5: Build Optimization - Analyze and optimize                          │
│                                                                                  │
│  Note: These exercises create configuration objects and simulate build          │
│  concepts. In real projects, you'd write actual config files.                   │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

console.log('Bundlers & Build Tools - Exercises');
console.log('====================================\n');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 1: Create a Webpack Configuration
// ════════════════════════════════════════════════════════════════════════════════

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Create a complete webpack.config.js for a React application                   │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Entry point: src/index.js                                                   │
│  2. Output: dist/bundle.[contenthash].js                                        │
│  3. Handle: .js, .jsx, .css, .scss, images, fonts                               │
│  4. Setup development server on port 3000                                       │
│  5. Enable source maps                                                          │
│  6. Configure path aliases (@components, @utils, @styles)                       │
│  7. Split vendor chunks                                                         │
│  8. Use production mode optimizations                                           │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

function createWebpackConfig(options = {}) {
  const {
    mode = 'production',
    port = 3000,
    srcDir = 'src',
    outputDir = 'dist',
  } = options;

  // TODO: Create the webpack configuration

  // Solution:
  const config = {
    // Mode
    mode: mode,

    // Entry
    entry: `./${srcDir}/index.js`,

    // Output
    output: {
      path: `/${outputDir}`, // Would use path.resolve(__dirname, outputDir)
      filename:
        mode === 'production' ? '[name].[contenthash:8].js' : '[name].js',
      chunkFilename:
        mode === 'production'
          ? '[name].[contenthash:8].chunk.js'
          : '[name].chunk.js',
      assetModuleFilename: 'assets/[name].[contenthash:8][ext]',
      clean: true,
      publicPath: '/',
    },

    // Module rules
    module: {
      rules: [
        // JavaScript/JSX
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env',
                ['@babel/preset-react', { runtime: 'automatic' }],
              ],
              plugins: ['@babel/plugin-transform-runtime'],
            },
          },
        },

        // CSS
        {
          test: /\.css$/,
          use: [
            mode === 'production'
              ? 'MiniCssExtractPlugin.loader'
              : 'style-loader',
            'css-loader',
            'postcss-loader',
          ],
        },

        // SCSS
        {
          test: /\.scss$/,
          use: [
            mode === 'production'
              ? 'MiniCssExtractPlugin.loader'
              : 'style-loader',
            {
              loader: 'css-loader',
              options: { modules: true },
            },
            'postcss-loader',
            'sass-loader',
          ],
        },

        // Images
        {
          test: /\.(png|jpg|jpeg|gif|svg|webp)$/i,
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024, // 8kb - inline if smaller
            },
          },
        },

        // Fonts
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'fonts/[name].[contenthash:8][ext]',
          },
        },
      ],
    },

    // Resolve
    resolve: {
      extensions: ['.js', '.jsx', '.json'],
      alias: {
        '@': `/${srcDir}`,
        '@components': `/${srcDir}/components`,
        '@utils': `/${srcDir}/utils`,
        '@styles': `/${srcDir}/styles`,
        '@hooks': `/${srcDir}/hooks`,
        '@assets': `/${srcDir}/assets`,
      },
    },

    // Plugins
    plugins: [
      // 'HtmlWebpackPlugin',
      // 'MiniCssExtractPlugin',
      // 'DefinePlugin',
      // 'CopyPlugin'
    ],

    // Dev Server
    devServer: {
      static: `./${outputDir}`,
      port: port,
      hot: true,
      open: true,
      historyApiFallback: true, // For SPA routing
      compress: true,
      client: {
        overlay: true,
        progress: true,
      },
    },

    // Optimization
    optimization: {
      minimize: mode === 'production',
      minimizer: [
        '...', // Use defaults
        'TerserPlugin',
        'CssMinimizerPlugin',
      ],
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
            priority: 20,
          },
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'react',
            chunks: 'all',
            priority: 30,
          },
          common: {
            minChunks: 2,
            priority: 10,
            reuseExistingChunk: true,
          },
        },
      },
      runtimeChunk: 'single',
    },

    // Source maps
    devtool: mode === 'production' ? 'source-map' : 'eval-source-map',

    // Performance hints
    performance: {
      hints: mode === 'production' ? 'warning' : false,
      maxEntrypointSize: 250000,
      maxAssetSize: 250000,
    },
  };

  return config;
}

// Test
console.log('Exercise 1 - Webpack Configuration:');
const webpackConfig = createWebpackConfig({ mode: 'production', port: 3000 });
console.log('  Mode:', webpackConfig.mode);
console.log('  Entry:', webpackConfig.entry);
console.log('  Output:', webpackConfig.output.filename);
console.log('  Aliases:', Object.keys(webpackConfig.resolve.alias).join(', '));
console.log('  Dev server port:', webpackConfig.devServer.port);
console.log(
  '  Chunk splitting:',
  webpackConfig.optimization.splitChunks ? 'enabled' : 'disabled'
);
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 2: Create a Vite Configuration
// ════════════════════════════════════════════════════════════════════════════════

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Create a vite.config.js for a Vue or React application                        │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Setup for React (or Vue)                                                    │
│  2. Configure development server with proxy                                     │
│  3. Setup path aliases                                                          │
│  4. Configure build output                                                      │
│  5. Add SCSS support                                                            │
│  6. Configure environment variables                                             │
│  7. Enable gzip compression for production                                      │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

function createViteConfig(options = {}) {
  const {
    framework = 'react',
    port = 5173,
    apiProxy = 'http://localhost:8080',
    outDir = 'dist',
  } = options;

  // TODO: Create the Vite configuration

  // Solution:
  const config = {
    // Plugins based on framework
    plugins: [
      framework === 'react' ? '@vitejs/plugin-react' : null,
      framework === 'vue' ? '@vitejs/plugin-vue' : null,
      // 'vite-plugin-compression' for gzip
    ].filter(Boolean),

    // Base path
    base: '/',

    // Server configuration
    server: {
      port: port,
      open: true,
      cors: true,

      // Proxy API requests
      proxy: {
        '/api': {
          target: apiProxy,
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
          secure: false,
        },
        '/ws': {
          target: apiProxy.replace('http', 'ws'),
          ws: true,
        },
      },

      // HMR configuration
      hmr: {
        overlay: true,
      },
    },

    // Build configuration
    build: {
      outDir: outDir,
      sourcemap: true,
      minify: 'esbuild',
      target: 'es2020',

      // Chunk size warning limit
      chunkSizeWarningLimit: 500,

      // Rollup options
      rollupOptions: {
        output: {
          // Manual chunk splitting
          manualChunks: {
            vendor: ['react', 'react-dom'],
            // or for Vue: ['vue', 'vue-router']
          },

          // Asset file names
          assetFileNames: 'assets/[name]-[hash][extname]',
          chunkFileNames: 'js/[name]-[hash].js',
          entryFileNames: 'js/[name]-[hash].js',
        },
      },

      // CSS code splitting
      cssCodeSplit: true,
    },

    // CSS configuration
    css: {
      // CSS modules
      modules: {
        localsConvention: 'camelCase',
      },

      // Preprocessor options
      preprocessorOptions: {
        scss: {
          // Global imports
          additionalData: `
                        @import "@/styles/variables.scss";
                        @import "@/styles/mixins.scss";
                    `,
        },
      },

      // PostCSS config
      postcss: {
        plugins: ['autoprefixer'],
      },
    },

    // Resolve configuration
    resolve: {
      alias: {
        '@': '/src',
        '@components': '/src/components',
        '@utils': '/src/utils',
        '@styles': '/src/styles',
        '@hooks': '/src/hooks',
        '@store': '/src/store',
      },
    },

    // Environment configuration
    envPrefix: 'VITE_',
    envDir: './',

    // Dependency optimization
    optimizeDeps: {
      include: ['lodash-es', 'axios'],
      exclude: [],
    },

    // Preview server (for production preview)
    preview: {
      port: 4173,
      open: true,
    },

    // Define global constants
    define: {
      __APP_VERSION__: JSON.stringify('1.0.0'),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    },
  };

  return config;
}

// Test
console.log('Exercise 2 - Vite Configuration:');
const viteConfig = createViteConfig({ framework: 'react', port: 5173 });
console.log('  Framework:', viteConfig.plugins[0] ? 'React' : 'Unknown');
console.log('  Server port:', viteConfig.server.port);
console.log('  API proxy:', viteConfig.server.proxy['/api'].target);
console.log('  Output dir:', viteConfig.build.outDir);
console.log('  Minifier:', viteConfig.build.minify);
console.log('  Env prefix:', viteConfig.envPrefix);
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 3: Create a Rollup Configuration for Library
// ════════════════════════════════════════════════════════════════════════════════

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Create a rollup.config.js for building a JavaScript library                   │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Multiple output formats: ESM, CJS, UMD                                      │
│  2. TypeScript support                                                          │
│  3. External dependencies (peer dependencies)                                   │
│  4. Minified production build                                                   │
│  5. Source maps                                                                 │
│  6. Banner with license info                                                    │
│  7. Preserve modules option for ESM                                             │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

function createRollupConfig(options = {}) {
  const {
    name = 'MyLibrary',
    input = 'src/index.ts',
    external = [],
    globals = {},
  } = options;

  // TODO: Create the Rollup configuration

  // Solution:
  const banner = `/*!
 * ${name} v1.0.0
 * (c) ${new Date().getFullYear()}
 * Released under the MIT License
 */`;

  const config = {
    // Input
    input: input,

    // External dependencies (don't bundle these)
    external: [
      ...external,
      'react',
      'react-dom',
      /^lodash/, // Regex to match lodash/*
    ],

    // Multiple outputs
    output: [
      // ESM format (modern bundlers, tree-shaking)
      {
        file: `dist/${name.toLowerCase()}.esm.js`,
        format: 'esm',
        sourcemap: true,
        banner: banner,
        exports: 'named',
      },

      // CommonJS format (Node.js)
      {
        file: `dist/${name.toLowerCase()}.cjs.js`,
        format: 'cjs',
        sourcemap: true,
        banner: banner,
        exports: 'named',
      },

      // UMD format (browsers + AMD + CommonJS)
      {
        file: `dist/${name.toLowerCase()}.umd.js`,
        format: 'umd',
        name: name,
        sourcemap: true,
        banner: banner,
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
          ...globals,
        },
      },

      // Minified UMD for production
      {
        file: `dist/${name.toLowerCase()}.umd.min.js`,
        format: 'umd',
        name: name,
        sourcemap: true,
        banner: banner,
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
          ...globals,
        },
        plugins: ['@rollup/plugin-terser'],
      },
    ],

    // Plugins
    plugins: [
      // Resolve node_modules
      '@rollup/plugin-node-resolve',

      // Convert CommonJS modules to ES6
      '@rollup/plugin-commonjs',

      // TypeScript support
      '@rollup/plugin-typescript',

      // Babel for additional transforms
      {
        name: '@rollup/plugin-babel',
        options: {
          babelHelpers: 'bundled',
          exclude: 'node_modules/**',
          extensions: ['.js', '.jsx', '.ts', '.tsx'],
        },
      },

      // JSON support
      '@rollup/plugin-json',

      // Replace environment variables
      {
        name: '@rollup/plugin-replace',
        options: {
          'process.env.NODE_ENV': JSON.stringify('production'),
          preventAssignment: true,
        },
      },
    ],

    // Watch options for development
    watch: {
      include: 'src/**',
      exclude: 'node_modules/**',
    },

    // Preserve modules for ESM (better tree-shaking in consumers)
    // For separate module files instead of single bundle:
    preserveModulesRoot: 'src',
  };

  // Alternative: Preserve modules configuration
  const preserveModulesConfig = {
    ...config,
    output: {
      dir: 'dist/esm',
      format: 'esm',
      sourcemap: true,
      preserveModules: true,
      preserveModulesRoot: 'src',
      entryFileNames: '[name].js',
    },
  };

  return { main: config, preserveModules: preserveModulesConfig };
}

// Test
console.log('Exercise 3 - Rollup Library Configuration:');
const rollupConfig = createRollupConfig({ name: 'AwesomeLib' });
console.log('  Input:', rollupConfig.main.input);
console.log(
  '  Output formats:',
  rollupConfig.main.output.map((o) => o.format).join(', ')
);
console.log(
  '  External deps:',
  rollupConfig.main.external.filter((e) => typeof e === 'string').join(', ')
);
console.log('  Plugins:', rollupConfig.main.plugins.length);
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 4: Implement Code Splitting
// ════════════════════════════════════════════════════════════════════════════════

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Implement code splitting strategies                                            │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Route-based code splitting                                                  │
│  2. Component-based lazy loading                                                │
│  3. Library/vendor splitting                                                    │
│  4. Conditional feature loading                                                 │
│  5. Preloading critical chunks                                                  │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

// Simulate dynamic import behavior
function createLazyLoader(importFn, options = {}) {
  const {
    preload = false,
    timeout = 10000,
    fallback = null,
    onError = null,
  } = options;

  let modulePromise = null;
  let loadedModule = null;

  return {
    // Preload the module
    preload() {
      if (!modulePromise) {
        modulePromise = importFn();
        modulePromise
          .then((mod) => {
            loadedModule = mod;
          })
          .catch((err) => {
            modulePromise = null;
            if (onError) onError(err);
          });
      }
      return modulePromise;
    },

    // Load and return the module
    async load() {
      if (loadedModule) return loadedModule;

      if (!modulePromise) {
        modulePromise = importFn();
      }

      // Add timeout
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Module load timeout')), timeout);
      });

      try {
        loadedModule = await Promise.race([modulePromise, timeoutPromise]);
        return loadedModule;
      } catch (error) {
        modulePromise = null;
        if (onError) onError(error);
        throw error;
      }
    },

    // Check if loaded
    isLoaded() {
      return loadedModule !== null;
    },
  };
}

// 1. Route-based code splitting
const routeModules = {
  home: createLazyLoader(
    () => Promise.resolve({ default: { name: 'HomePage' } }),
    { preload: true }
  ),
  dashboard: createLazyLoader(() =>
    Promise.resolve({ default: { name: 'DashboardPage' } })
  ),
  settings: createLazyLoader(() =>
    Promise.resolve({ default: { name: 'SettingsPage' } })
  ),
  profile: createLazyLoader(() =>
    Promise.resolve({ default: { name: 'ProfilePage' } })
  ),
};

async function loadRoute(routeName) {
  const loader = routeModules[routeName];
  if (!loader) {
    throw new Error(`Unknown route: ${routeName}`);
  }

  const module = await loader.load();
  return module.default;
}

// 2. Component-based lazy loading
function createLazyComponent(importFn) {
  let Component = null;
  let loading = false;
  let error = null;

  return {
    // Render function (simplified)
    render(props) {
      if (Component) {
        return { type: Component, props };
      }
      if (loading) {
        return { type: 'Loading', props: {} };
      }
      if (error) {
        return { type: 'Error', props: { error } };
      }

      loading = true;
      importFn()
        .then((mod) => {
          Component = mod.default;
          loading = false;
        })
        .catch((err) => {
          error = err;
          loading = false;
        });

      return { type: 'Loading', props: {} };
    },

    // Preload the component
    preload() {
      return importFn();
    },
  };
}

// 3. Feature-based conditional loading
async function loadFeatureIfEnabled(featureName, features = {}) {
  if (!features[featureName]) {
    console.log(`  Feature "${featureName}" is disabled`);
    return null;
  }

  const featureModules = {
    analytics: () =>
      Promise.resolve({
        init: () => console.log('  Analytics initialized'),
        track: (event) => console.log(`  Tracked: ${event}`),
      }),
    chat: () =>
      Promise.resolve({
        init: () => console.log('  Chat widget initialized'),
        open: () => console.log('  Chat opened'),
      }),
    notifications: () =>
      Promise.resolve({
        init: () => console.log('  Notifications initialized'),
        show: (msg) => console.log(`  Notification: ${msg}`),
      }),
  };

  const loader = featureModules[featureName];
  if (!loader) {
    throw new Error(`Unknown feature: ${featureName}`);
  }

  console.log(`  Loading feature: ${featureName}`);
  return loader();
}

// 4. Preloading strategy
class PreloadManager {
  constructor() {
    this.preloaded = new Set();
    this.queue = [];
  }

  // Add to preload queue
  enqueue(name, importFn, priority = 0) {
    this.queue.push({ name, importFn, priority });
    this.queue.sort((a, b) => b.priority - a.priority);
  }

  // Preload on idle
  async preloadOnIdle() {
    // In browser: requestIdleCallback
    // Simulating with setTimeout
    while (this.queue.length > 0) {
      const { name, importFn } = this.queue.shift();

      if (this.preloaded.has(name)) continue;

      try {
        await importFn();
        this.preloaded.add(name);
        console.log(`    Preloaded: ${name}`);
      } catch (error) {
        console.log(`    Failed to preload: ${name}`);
      }

      // Yield to main thread
      await new Promise((resolve) => setTimeout(resolve, 0));
    }
  }

  // Preload on link hover (for routes)
  onLinkHover(routeName) {
    const loader = routeModules[routeName];
    if (loader && !loader.isLoaded()) {
      loader.preload();
    }
  }
}

// Test
console.log('Exercise 4 - Code Splitting:');

(async () => {
  // Route-based loading
  console.log('  Route-based loading:');
  const homePage = await loadRoute('home');
  console.log(`    Loaded: ${homePage.name}`);

  // Feature loading
  console.log('  Feature loading:');
  const features = { analytics: true, chat: false };
  await loadFeatureIfEnabled('analytics', features);
  await loadFeatureIfEnabled('chat', features);

  // Preload manager
  console.log('  Preload manager:');
  const preloader = new PreloadManager();
  preloader.enqueue('dashboard', () => Promise.resolve(), 1);
  preloader.enqueue('settings', () => Promise.resolve(), 0);
  await preloader.preloadOnIdle();

  console.log('');
})();

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 5: Build Optimization Analysis
// ════════════════════════════════════════════════════════════════════════════════

/*
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Create a build analyzer and optimization checker                              │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Analyze bundle composition                                                  │
│  2. Detect optimization opportunities                                           │
│  3. Check for common issues (duplicate deps, large chunks)                      │
│  4. Suggest improvements                                                        │
│  5. Generate a report                                                           │
│                                                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
*/

class BuildAnalyzer {
  constructor() {
    this.chunks = [];
    this.issues = [];
    this.suggestions = [];
  }

  // Add a chunk for analysis
  addChunk(chunk) {
    this.chunks.push({
      name: chunk.name,
      size: chunk.size,
      modules: chunk.modules || [],
      type: chunk.type || 'chunk',
    });
  }

  // Analyze chunks for issues
  analyze() {
    this.issues = [];
    this.suggestions = [];

    // Check total bundle size
    const totalSize = this.chunks.reduce((sum, c) => sum + c.size, 0);
    if (totalSize > 500 * 1024) {
      // 500KB
      this.issues.push({
        type: 'size',
        severity: 'warning',
        message: `Total bundle size (${this.formatSize(
          totalSize
        )}) exceeds recommended limit (500KB)`,
      });
      this.suggestions.push('Consider code splitting or lazy loading');
    }

    // Check individual chunk sizes
    for (const chunk of this.chunks) {
      if (chunk.size > 244 * 1024) {
        // 244KB
        this.issues.push({
          type: 'chunk-size',
          severity: 'warning',
          message: `Chunk "${chunk.name}" (${this.formatSize(
            chunk.size
          )}) is too large`,
        });
        this.suggestions.push(`Split "${chunk.name}" into smaller chunks`);
      }
    }

    // Check for duplicate modules
    const allModules = this.chunks.flatMap((c) => c.modules);
    const moduleCounts = {};
    for (const mod of allModules) {
      moduleCounts[mod] = (moduleCounts[mod] || 0) + 1;
    }

    for (const [mod, count] of Object.entries(moduleCounts)) {
      if (count > 1) {
        this.issues.push({
          type: 'duplicate',
          severity: 'error',
          message: `Module "${mod}" appears in ${count} chunks`,
        });
        this.suggestions.push(`Extract "${mod}" to a shared chunk`);
      }
    }

    // Check for vendor splitting
    const hasVendorChunk = this.chunks.some((c) => c.name.includes('vendor'));
    if (!hasVendorChunk && this.chunks.length > 1) {
      this.issues.push({
        type: 'optimization',
        severity: 'info',
        message: 'No vendor chunk detected',
      });
      this.suggestions.push(
        'Split node_modules into a separate vendor chunk for better caching'
      );
    }

    return this.generateReport();
  }

  // Check for common problematic dependencies
  checkDependencies(dependencies) {
    const heavyDeps = {
      moment: { size: 232, alternative: 'dayjs or date-fns' },
      lodash: { size: 72, alternative: 'lodash-es with tree-shaking' },
      axios: { size: 14, alternative: 'native fetch or ky' },
    };

    for (const dep of dependencies) {
      if (heavyDeps[dep]) {
        const info = heavyDeps[dep];
        this.suggestions.push(
          `Consider replacing "${dep}" (~${info.size}KB) with ${info.alternative}`
        );
      }
    }
  }

  // Format file size
  formatSize(bytes) {
    if (bytes < 1024) return `${bytes}B`;
    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
    return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
  }

  // Generate optimization report
  generateReport() {
    const totalSize = this.chunks.reduce((sum, c) => sum + c.size, 0);

    return {
      summary: {
        totalChunks: this.chunks.length,
        totalSize: this.formatSize(totalSize),
        totalSizeBytes: totalSize,
        issueCount: this.issues.length,
        suggestionCount: this.suggestions.length,
      },
      chunks: this.chunks.map((c) => ({
        name: c.name,
        size: this.formatSize(c.size),
        percentage: ((c.size / totalSize) * 100).toFixed(1) + '%',
      })),
      issues: this.issues,
      suggestions: [...new Set(this.suggestions)], // Unique suggestions
      score: this.calculateScore(),
    };
  }

  // Calculate optimization score
  calculateScore() {
    let score = 100;

    for (const issue of this.issues) {
      if (issue.severity === 'error') score -= 15;
      if (issue.severity === 'warning') score -= 10;
      if (issue.severity === 'info') score -= 5;
    }

    return Math.max(0, score);
  }
}

// Test
console.log('Exercise 5 - Build Analysis:');

setTimeout(() => {
  const analyzer = new BuildAnalyzer();

  // Simulate bundle chunks
  analyzer.addChunk({
    name: 'main',
    size: 180 * 1024,
    modules: ['app', 'utils'],
  });
  analyzer.addChunk({
    name: 'vendor',
    size: 320 * 1024,
    modules: ['react', 'lodash'],
  });
  analyzer.addChunk({
    name: 'dashboard',
    size: 95 * 1024,
    modules: ['chart', 'utils'],
  }); // utils duplicate!
  analyzer.addChunk({ name: 'settings', size: 45 * 1024, modules: ['forms'] });

  // Check dependencies
  analyzer.checkDependencies(['react', 'moment', 'lodash', 'axios']);

  // Generate report
  const report = analyzer.analyze();

  console.log('  Bundle Analysis Report:');
  console.log('  ─────────────────────────');
  console.log(`  Total Size: ${report.summary.totalSize}`);
  console.log(`  Chunks: ${report.summary.totalChunks}`);
  console.log(`  Score: ${report.score}/100`);
  console.log('');
  console.log('  Chunk Breakdown:');
  report.chunks.forEach((c) => {
    console.log(`    ${c.name}: ${c.size} (${c.percentage})`);
  });
  console.log('');
  console.log('  Issues:');
  report.issues.forEach((i) => {
    console.log(`    [${i.severity.toUpperCase()}] ${i.message}`);
  });
  console.log('');
  console.log('  Suggestions:');
  report.suggestions.forEach((s, i) => {
    console.log(`    ${i + 1}. ${s}`);
  });
  console.log('');

  // ════════════════════════════════════════════════════════════════════════════════
  // BONUS: Create package.json scripts
  // ════════════════════════════════════════════════════════════════════════════════

  console.log('Bonus - Package.json Scripts Generator:');

  function generateScripts(bundler, options = {}) {
    const scripts = {};

    switch (bundler) {
      case 'webpack':
        scripts.dev = 'webpack serve --mode development';
        scripts.build = 'webpack --mode production';
        scripts['build:dev'] = 'webpack --mode development';
        scripts.analyze =
          'webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json';
        break;

      case 'vite':
        scripts.dev = 'vite';
        scripts.build = 'vite build';
        scripts.preview = 'vite preview';
        scripts['build:analyze'] = 'vite build --mode analyze';
        break;

      case 'rollup':
        scripts.dev = 'rollup -c -w';
        scripts.build = 'rollup -c';
        scripts['build:prod'] = 'NODE_ENV=production rollup -c';
        break;

      case 'esbuild':
        scripts.dev = 'esbuild src/index.js --bundle --watch --servedir=public';
        scripts.build =
          'esbuild src/index.js --bundle --minify --outfile=dist/bundle.js';
        break;
    }

    // Common scripts
    scripts.lint = 'eslint src/**/*.{js,jsx,ts,tsx}';
    scripts['lint:fix'] = 'eslint src/**/*.{js,jsx,ts,tsx} --fix';
    scripts.test = 'vitest';
    scripts['test:coverage'] = 'vitest --coverage';
    scripts.typecheck = 'tsc --noEmit';
    scripts.format = 'prettier --write src/**/*.{js,jsx,ts,tsx,css,scss}';

    return scripts;
  }

  const viteScripts = generateScripts('vite');
  console.log('  Generated Vite scripts:');
  Object.entries(viteScripts).forEach(([name, cmd]) => {
    console.log(`    "${name}": "${cmd}"`);
  });

  console.log(`
╔══════════════════════════════════════════════════════════════════════════════╗
ā•‘              BUNDLERS & BUILD TOOLS - EXERCISES COMPLETE                     ā•‘
╠══════════════════════════════════════════════════════════════════════════════╣
ā•‘                                                                              ā•‘
ā•‘  Completed Exercises:                                                        ā•‘
ā•‘  āœ“ Exercise 1: Webpack Configuration                                        ā•‘
ā•‘  āœ“ Exercise 2: Vite Configuration                                           ā•‘
ā•‘  āœ“ Exercise 3: Rollup Library Build                                         ā•‘
ā•‘  āœ“ Exercise 4: Code Splitting Strategies                                    ā•‘
ā•‘  āœ“ Exercise 5: Build Optimization Analysis                                  ā•‘
ā•‘  āœ“ Bonus: Package.json Scripts Generator                                    ā•‘
ā•‘                                                                              ā•‘
ā•‘  Key Takeaways:                                                              ā•‘
ā•‘  • Choose bundler based on use case (app vs library)                         ā•‘
ā•‘  • Configure loaders/plugins for asset handling                             ā•‘
ā•‘  • Implement code splitting for better performance                          ā•‘
ā•‘  • Analyze and optimize bundle size regularly                               ā•‘
ā•‘  • Use content hashes for cache busting                                     ā•‘
ā•‘  • Split vendor code for better caching                                     ā•‘
ā•‘                                                                              ā•‘
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
`);
}, 100); // Delay to let async code finish
Exercises - JavaScript Tutorial | DeepML