webpack配置推荐 - xw332/xw332.github.io GitHub Wiki

webpack 官网起步指南

webpack 中文起步指南

中文网的部分配置已经失效,可以参考英文官网:

clean-webpack-plugin

webpack.optimize.CommonsChunkPlugin

tree-shaking

缓存部分

扩展链接:

遗留问题:

  • html和css在引用同一个小图时,会以重复的base64代码包含在各自的文件里(考虑这类图片单独文件夹并保持链接形式)
  • html-withimg-loader不支持ejs,会导致html-webpack-plugin解析变量失败(考虑通过require方式引入图片)
  • 图片和css最终输出没有压缩
  • image-webpack-loader做图片压缩,会build失败,需要用cnpm的源重新安装一遍才能成功(神)。另外默认的压缩比例,居然让图片变大,放弃。

我的webpack-demo配置

// package.json
{
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config config/webpack.dev.js",
    "build:dev": "webpack --config config/webpack.dev.js",
    "build": "NODE_ENV=production webpack --config config/webpack.prod.js",
    "lint": "eslint --ext .js --cache --fix dev config",
    "precommit": "npm run lint && npm run build",
    "browserslist": "browserslist"
  },
  "dependencies": {
    "@babel/runtime": "^7.7.7",
    "axios": "^0.19.0",
    "mockjs": "^1.1.0",
    "mockjs-fetch": "^1.0.3",
    "query-string": "^6.9.0"
  },
  "devDependencies": {
    "@babel/core": "^7.7.7",
    "@babel/plugin-transform-runtime": "^7.7.6",
    "@babel/preset-env": "^7.7.7",
    "@babel/runtime-corejs3": "^7.7.7",
    "autoprefixer": "^9.7.3",
    "babel-eslint": "^11.0.0-beta.2",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^5.1.1",
    "css-loader": "^3.4.1",
    "cssnano": "^4.1.10",
    "eslint": "^6.8.0",
    "eslint-config-prettier": "^6.9.0",
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-plugin-babel": "^5.3.0",
    "eslint-plugin-import": "^2.19.1",
    "eslint-plugin-prettier": "^3.1.2",
    "file-loader": "^5.0.2",
    "glob": "^7.1.6",
    "happypack": "^5.0.1",
    "html-webpack-plugin": "^4.0.0-beta.11",
    "html-withimg-loader": "^0.1.16",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "postcss-loader": "^3.0.0",
    "prettier": "^1.19.1",
    "style-loader": "^1.1.2",
    "uglify-es": "^3.3.9",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1",
    "webpack-merge": "^4.2.2",
    "workbox-webpack-plugin": "^4.3.1"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8",
    "Android >= 4.0"
  ]
}
// webpack.common.js
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HappyPack = require('happypack');
const loadMinified = require('./load-minified');
const autoprefixer = require('autoprefixer');

function resolve(dir) {
  return path.resolve(__dirname, '..', dir);
}

const pathSrc = 'dev/public';
const pathDist = 'src/static/public';
const devMode = process.env.NODE_ENV !== 'production';

let entry = {};
let plugins = [];
function makeEntry() {
  entry = glob.sync('./dev/**/js/index.js').reduce((map, relativePath) => {
    const chunk = relativePath.match(/^\.\/dev\/([^/]+)/)[1];
    map[chunk] = relativePath;
    return map;
  }, {});
  plugins = [
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [resolve(pathDist)],
    }),
    new CopyWebpackPlugin([
      { from: `${pathSrc}/favicon.ico`, to: '../html' },
    ]),
    // new webpack.DllReferencePlugin({
    //   manifest: require('../dll/vendors-manifest.json'),
    // }),
    new MiniCssExtractPlugin({
      filename: devMode ? '../style/[name].css' : '../style/[name].[chunkhash:8].css',
      chunkFilename: devMode ? '../style/[name].css' : '../style/[name].[chunkhash:8].css',
    }),
    new HappyPack({
      id: 'js',
      loaders: [
        {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/transform-runtime'],
            cacheDirectory: true,
            cacheCompression: true,
            compact: true,
          },
        },
      ],
    }),
  ];
  Object.keys(entry).forEach(function(chunk) {
    plugins.push(
      new HtmlWebpackPlugin({
        hash: devMode, // 内嵌js和css加hash
        chunks: [chunk],
        filename: `../html/${chunk}.html`,
        template: `html-withimg-loader?min=false!./dev/${chunk}/index.html`,
        serviceWorkerLoader: `<script>${loadMinified(path.resolve(__dirname, 'service-worker-prod.js'))}</script>`,
        minify: devMode
          ? false
          : {
              collapseWhitespace: true,
              removeComments: true,
              // removeAttributeQuotes: true,
              removeRedundantAttributes: true,
              removeScriptTypeAttributes: true,
              removeStyleLinkTypeAttributes: true,
              minifyCSS: true,
              minifyJS: true,
            },
      })
    );
  });
}
makeEntry();

module.exports = {
  entry,
  output: {
    path: resolve(`${pathDist}/js`),
    filename: '[name].[chunkhash:8].js',
    chunkFilename: '../js/[name].[chunkhash:8].js',
    // publicPath: '/',
  },
  resolve: {
    extensions: ['.js', '.json'],
    alias: {
      '@': resolve(pathSrc),
      '@style': resolve(`${pathSrc}/style`),
      '@img': resolve(`${pathSrc}/img`),
    },
  },
  plugins,
  module: {
    rules: [
      {
        test: /\.js$/,
        include: resolve('dev'),
        exclude: /node_modules/,
        use: ['happypack/loader?id=js'],
      },
      {
        test: /\.(css|less)$/,
        include: resolve('dev'),
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: devMode,
            },
          },
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [autoprefixer()],
            },
          },
          'less-loader',
        ],
      },
      {
        test: /\.(png|gif|jpe?g|svg)$/,
        include: resolve('dev'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name() {
                // 保持和开发目录一致
                if (devMode) return '[path][name].[ext]';
                return '[name].[contenthash:8].[ext]';
              },
              outputPath: devMode
                ? '../img'
                : (name, resourcePath, context) => {
                    const relativePath = path.relative(`${context}/dev`, resourcePath);
                    const [pathName] = relativePath.match(/^[^/]+/);
                    return `../img/${pathName}/${name}`;
                  },
              // publicPath: '../img',
              esModule: false,
              limit: 8192,
            },
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        include: resolve('dev'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name() {
                if (devMode) return '[path][name].[ext]';
                return '[name].[contenthash:8].[ext]';
              },
              outputPath: devMode
                ? '../icon'
                : (name, resourcePath, context) => {
                    const relativePath = path.relative(`${context}/dev`, resourcePath);
                    const [pathName] = relativePath.match(/^[^/]+/);
                    return `../icon/${pathName}/${name}`;
                  },
              // publicPath: '../icon',
              esModule: false,
            },
          },
        ],
      },
    ],
  },
};
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    vendors: [
      'react',
      'react-dom',
      'react-router',
      'react-router-dom',
      'redux',
      'react-loadable',
      'react-helmet',
      'i18next',
      'react-i18next',
    ],
  },
  output: {
    path: path.join(__dirname, '..', 'dll'),
    filename: '[name].dll.js',
    library: '[name]_library',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      path: path.join(__dirname, '..', 'dll/[name]-manifest.json'),
      name: '[name]_library',
    }),
  ],
};
// webpack.prod.js
const merge = require('webpack-merge');
const WorkboxPlugin = require('workbox-webpack-plugin');
const common = require('./webpack.common');

module.exports = merge(common, {
  plugins: [
    new WorkboxPlugin.GenerateSW({
      cacheId: 'h5-home',
      importWorkboxFrom: 'local',
      swDest: 'service-worker.js',
      clientsClaim: true,
      skipWaiting: true,
      exclude: [/\.html$/],
    }),
  ],
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 10000,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
});
// webpack.dev.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common');

function resolve(dir) {
  return path.resolve(__dirname, '..', dir);
}

module.exports = merge(common, {
  mode: 'development',
  devServer: {
    contentBase: resolve('src/static/public'),
  },
});
⚠️ **GitHub.com Fallback** ⚠️