webpack 多入口打包

88 阅读1分钟

webpack 多入口构建(基础版)

config/webpack.dev.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { resolveApp, getEntryApp } = require('./tools')
const { entry, htmlWebpackPlugins } = getEntryApp()

module.exports = {
  target: 'web',
  mode: 'development',
  entry: {
    main: './src/main.js',
    ...entry,
  },
  output: {
    filename: 'js/[name].[chunkhash:6].build.js',
    path: resolveApp('dist'),
  },
  devServer: { hot: true },
  resolve: {
    extensions: ['.js', '.html', '.json'],
    alias: { '@': resolveApp('src') },
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
        ],
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
          'less-loader',
        ],
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: { filename: 'images/[name].[hash:4][ext]' },
        parser: { dataUrlCondition: { maxSize: 30 * 1024 } },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: { filename: 'fonts/[name].[hash:3][ext]' },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ title: 'WEBPACK-CONFIG', template: './public/index.html', chunks: ['main'] }),
    new CopyWebpackPlugin({ patterns: [{ from: 'public', globOptions: { ignore: ['**/index.html'] } }] }),
    ...htmlWebpackPlugins,
  ],
}

config/webpack.prod.js

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { resolveApp, getEntryApp } = require('./tools')
const { entry, htmlWebpackPlugins } = getEntryApp()

module.exports = {
  mode: 'production',
  entry: {
    main: './src/main.js',
    ...entry,
  },
  output: {
    filename: 'js/[name].[chunkhash:6].build.js',
    path: resolveApp('dist'),
  },
  devServer: { hot: true },
  resolve: {
    extensions: ['.js', '.html', '.json'],
    alias: { '@': resolveApp('src') },
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
        ],
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
          'less-loader',
        ],
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: { filename: 'images/[name].[hash:4][ext]' },
        parser: { dataUrlCondition: { maxSize: 30 * 1024 } },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: { filename: 'fonts/[name].[hash:3][ext]' },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({ title: 'WEBPACK-CONFIG', template: './public/index.html', chunks: ['main'] }),
    new CopyWebpackPlugin({ patterns: [{ from: 'public', globOptions: { ignore: ['**/index.html'] } }] }),
    ...htmlWebpackPlugins,
  ],
}

config/tools.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const glob = require('glob')
const path = require('path')
const appDir = process.cwd()

/**
 * @description 获取文件路径
 */
const resolveApp = relativePath => {
  return path.resolve(appDir, relativePath)
}

/**
 * @description 自动获取 html 下的 js、html 文件
 */
const getEntryApp = () => {
  const entry = {}
  const htmlWebpackPlugins = []

  const entryFiles = glob.sync(resolveApp('./src/html/**/index.js'))
  entryFiles.forEach(item => {
    const match = item.match(/src\/(.*)\/index\.js$/)
    const pageName = match?.[1]
    const name = pageName.split('/')[1]
    entry[name] = item
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        filename: `${pageName}.html`,
        template: resolveApp(`src/${pageName}/index.html`),
        chunks: [name],
      }),
    )
  })

  return { entry, htmlWebpackPlugins }
}

module.exports = {
  resolveApp,
  getEntryApp,
}

package.json

{
  "name": "webpack-build",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack serve --config config/dev.config.js",
    "build": "webpack --config config/pro.config.js"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.5",
    "autoprefixer": "^10.4.14",
    "babel-loader": "^9.1.2",
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.3",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.5.1",
    "less": "^4.1.3",
    "less-loader": "^11.1.0",
    "postcss": "^8.4.23",
    "postcss-loader": "^7.2.4",
    "postcss-preset-env": "^8.3.2",
    "style-loader": "^3.3.2",
    "url-loader": "^4.1.1",
    "webpack": "^5.79.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.13.3"
  },
  "dependencies": {
    "core-js": "^3.30.1",
    "regenerator-runtime": "^0.13.11"
  }
}

.browserslistrc

> 1%
last 2 version
not dead

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // false: 不对当前的 JS 处理做 polyfill 填充
        // usage: 依据用户源代码中所使用到的新语法进行填充
        // entry: 依据我们当前筛选出来的浏览器来决定填充什么
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

webpack 多入口构建(完整版)

upload_5aa7cc46a93776b8d9c48ab2622103b7.png

package.json

{
  "name": "myzony-effect",
  "version": "1.0.1",
  "description": "js、css 交互效果",
  "main": "index.ts",
  "author": "myzony@qq.com",
  "license": "MIT",
  "scripts": {
    "dev": "npm run start",
    "start": "webpack serve --config config/webpack.common.js --env development",
    "build": "webpack --config config/webpack.common.js --env production"
  },
  "dependencies": {
    "core-js": "^3.30.1",
    "regenerator-runtime": "^0.13.11"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.5",
    "autoprefixer": "^10.4.14",
    "babel-loader": "^9.1.2",
    "clean-webpack-plugin": "^4.0.0",
    "compression-webpack-plugin": "^10.0.0",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^5.0.0",
    "dart-sass": "^1.25.0",
    "file-loader": "^6.2.0",
    "html-loader": "^5.0.0",
    "html-webpack-plugin": "^5.5.1",
    "mini-css-extract-plugin": "^2.7.6",
    "postcss": "^8.4.23",
    "postcss-loader": "^7.2.4",
    "postcss-preset-env": "^8.3.2",
    "sass": "^1.63.2",
    "sass-loader": "^13.3.1",
    "style-loader": "^3.3.2",
    "terser-webpack-plugin": "^5.3.8",
    "url-loader": "^4.1.1",
    "webpack": "^5.79.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.13.3",
    "webpack-merge": "^5.8.0"
  }
}

postcss.config.js

const autoprefixer = require('autoprefixer')
const postcssENV = require('postcss-preset-env')

module.exports = {
  plugins: [postcssENV, autoprefixer],
}

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // false: 不对当前的 JS 处理做 polyfill 填充
        // usage: 依据用户源代码中所使用到的新语法进行填充
        // entry: 依据我们当前筛选出来的浏览器来决定填充什么
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

tools.js

// config/tools.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const glob = require('glob')
const path = require('path')
const appDir = process.cwd()

/**
 * @description 获取文件路劲
 */
const resolveApp = relativePath => {
  return path.resolve(appDir, relativePath)
}

const getEntryApp = () => {
  const entry = {}
  const htmlWebpackPlugins = []

  const entryFiles = glob.sync(resolveApp('./src/html/**/index.js'))
  entryFiles.forEach(item => {
    const match = item.match(/src\/(.*)\/index\.js$/)
    const pageName = match?.[1]
    const name = pageName.split('/')[1]
    entry[name] = item
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        filename: `${pageName}.html`,
        template: resolveApp(`src/${pageName}/index.html`),
        chunks: [name],
      }),
    )
  })

  return { entry, htmlWebpackPlugins }
}

module.exports = {
  resolveApp,
  getEntryApp,
}

webpack.common.js

// config/webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // css 压缩抽离
const { merge } = require('webpack-merge')
const { resolveApp, getEntryApp } = require('./tools')
const { entry, htmlWebpackPlugins } = getEntryApp()
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')

// console.log(entry, htmlWebpackPlugins)

const commonConfig = isProduction => ({
  entry,
  output: {
    filename: 'js/[name].[chunkhash:6].build.js',
    path: resolveApp('dist'),
  },
  devServer: { hot: true },
  resolve: {
    extensions: ['.js', '.html', '.json'],
    alias: { '@': resolveApp('src') },
  },
  optimization: {
    runtimeChunk: true, // 多入口打包设置为 single https://www.webpackjs.com/configuration/optimization/#optimizationruntimechunk
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: { importLoaders: 1, esModule: false },
          },
          'postcss-loader',
          {
            loader: 'sass-loader',
            options: { additionalData: '@import "~@/assets/styles/global.scss";' },
          },
        ],
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: { filename: 'images/[name].[hash:4][ext]' },
        parser: { dataUrlCondition: { maxSize: 30 * 1024 } },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: { filename: 'fonts/[name].[hash:3][ext]' },
      },
      {
        test: /\.(cur)$/,
        use: [
          {
            loader: 'url-loader',
            options: { name: 'images/[name].[hash:6].[ext]', limit: 25 * 1024 },
          },
        ],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ title: '前端效果', template: './public/index.html', chunks: ['main'] }),
    new CopyWebpackPlugin({ patterns: [{ from: 'public', globOptions: { ignore: ['**/index.html'] } }] }),
    new MiniCssExtractPlugin({ filename: 'css/[name].[hash:8].css' }), // css 压缩抽离
    ...htmlWebpackPlugins,
  ],
})

module.exports = env => {
  const isProduction = env.production
  process.env.NODE_ENV = isProduction ? 'production' : 'development'
  const config = isProduction ? prodConfig : devConfig
  const mergeConfig = merge(commonConfig(isProduction), config)

  // console.log('mergeConfig =', mergeConfig)
  return mergeConfig
}

webpack.dev.js

// config/webpack.dev.js
module.exports = {
  target: 'web',
  mode: 'development',
  devtool: 'source-map',
  entry: { main: './src/main.js' },
  devServer: {
    hot: 'only', // 开启热更新,报错刷新不会刷新整个页面
    port: 3399, // 端口号
    open: false, // 自动打开浏览器
    compress: true, // 启用 gzip 压缩
    // proxy: {
    //   '/api': {
    //     target: 'https://api.github.com',
    //     pathRewrite: {'^/api': ''}, // 将 https:/api.github.com/api 重写为 https:/api.github.com
    //     changeOrigin: true, // 默认情况下,代理时会保留主机头的来源,可以将 changeOrigin 设置为 true 以覆盖此行为
    //   }
    // }
  },
}

webpack.prod.js

// config/webpack.prod.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') // 对抽离的 css 进行压缩
const CompressionPlugin = require('compression-webpack-plugin') // 对资源进行 gzip 压缩处理
const TerserPlugin = require('terser-webpack-plugin') // 用于处理 js 的压缩和混淆,通过 webpack plugin 的方式对代码进行处理

module.exports = {
  mode: 'production',
  devtool: false,
  performance: { hints: false },
  entry: { main: './src/main.js' },
  optimization: {
    minimize: true, // 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer定义的插件压缩 bundle
    usedExports: true, // js 树摇
    minimizer: [
      new CssMinimizerPlugin(), // 对抽离的 css 进行压缩
      new TerserPlugin({ extractComments: false }), // extractComments 是否需要生成 liscens 文件,将注释剥离到单独的文件中
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({ patterns: [{ from: 'public', globOptions: { ignore: ['**/index.html'] } }] }),
    new CompressionPlugin({ test: /\.(css|js)$/ }), // 正则匹配进行压缩
  ],
}

注意

- border
    - index.html
    - index.js
    - index.scss

由于 webpack 打包是从 js 开始编译,所以 scss 需要在 js 中引入,否则不会编译成 css。多个模块需要建立多个文件夹,每个都有一个 index.html,index.js 中引入 index.scss。