Skip to content

sobird/webpack-tutorial

Repository files navigation

webpack5 配置

本文档从零开始配置 react + typescript 项目。

安装webpack

  • webpack webpack 打包工具

  • webpack-cli webpack 命令行接口

  • webpack-dev-server 在本地启动一个http服务,运行应用

yarn add -D webpack webpack-cli webpack-dev-server

webpack.config.js

在项目根目录创建 webpack.config.js 配置文件,基本结构如下:

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  // webpack config
};

module.exports = () => {
  if (isProduction) {
    config.mode = 'production';

  } else {
    config.mode = 'development';
  }
  return config;
}

公共配置

配置 entryoutputdevtooldevServerresolve

{
  devtool: isProduction ? false : 'inline-source-map',
  entry: {
    app: [
      './src/index.tsx',
    ],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    // pathinfo: true,
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
    publicPath: '/',
  },
  devServer: {
    open: true,
    host: 'localhost',
    port: 3000,
    hot: true, // 热更新
    historyApiFallback: true,
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
    alias: {
      '@': path.join(__dirname, 'src'),
    },
  }
}

热更新

在入口文件 src/index.js 尾部添加:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

// 支持热更新功能
module?.hot?.accept();

为防止 ts 报错,安装 @types/webpack-env

yarn add -D @types/webpack-env

scripts

配置 package.json 中的 scripts 字段,添加start、build、lint等命令

  "scripts": {
    "start": "NODE_ENV=development webpack serve",
    "build": "webpack --mode=production --node-env=production",
    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
  },

plugin

html-webpack-plugin

创建应用入口html,同时自动添加 bundle.js 文件,通过 yarn add -D html-webpack-plugin 命令安装,基本配置如下:

const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve('public/index.html'),
      filename: 'index.html',
      minify: true,
      inject: true,
      title: 'Webpack App',
    }),
  ]
}

dotenv-webpack

为项目添加 process.env 环境变量,通过 yarn add -D dotenv-webpack 命令安装,基本配置如下:

const Dotenv = require('dotenv-webpack');

const config = {
  plugins: [
    new Dotenv({
      path: path.join(__dirname, `.env.${process.env.NODE_ENV}`),
      safe: true,
      // hide any errors
      silent: true,
      // load all the predefined 'process.env' variables which will trump anything local per dotenv specs.
      systemvars: true,
      // Allows your variables to be "expanded" for reusability within your .env file.
      expand: true,
      // allow empty variables (e.g. `FOO=`) (treat it as empty string, rather than missing)
      allowEmptyValues: true,
      // load '.env.defaults' as the default values if empty.
      defaults: path.join(__dirname, '.env.defaults'),
    }),
  ]
}

clean-webpack-plugin

webpack 5.20.0+ 版本中通过如下配置,可替换 clean-webpack-plugin 插件功能。

module.exports = {
  //...
  output: {
    clean: true, // 在生成文件之前清空 output 目录
  },
};

mini-css-extract-plugin

将CSS提取到单独的CSS文件中,通过 yarn add -D mini-css-extract-plugin 安装,基本配置如下:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
...
module.exports = () => {
  if (isProduction) {
    config.mode = 'production';

    config.plugins.push(new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[id].[contenthash].css',
    }));
  } else {
    config.mode = 'development';
  }
  return config;
};

css-minimizer-webpack-plugin

css压缩优化,通过 yarn add -D css-minimizer-webpack-plugin 安装,基本配置如下:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const config = {
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
};

terser-webpack-plugin

terser-webpack-plugin 内部封装了 terser 库,用于处理 js 的压缩和混淆,通过 webpack plugin 的方式对代码进行处理。

webpack v5 开箱即带有最新版本的 terser-webpack-plugin。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    // 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer定义的插件压缩 bundle。
    minimize: true,
    // 允许你通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
    minimizer: [new TerserPlugin()],
  },
};

webpack.ProgressPlugin

在打包构建过程中输出当前的进度信息

const webpack = require('webpack');

const config = {
  plugins: [
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      handler(percentage, message, ...args) {
        // custom logic
      },
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: null,
    })
  ]
}

purgecss-webpack-plugin

去除无用样式,通过 `yarn add -D purgecss-webpack-plugin`安装,基本配置如下:

const glob = require('glob');
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin');

const config = {
  plugins: [
    new PurgeCSSPlugin({
      paths: glob.sync(`${path.resolve(__dirname, './src')}/**/*.{tsx,scss,less,css}`, { nodir: true }),
      whitelist: ['html', 'body']
    }),
  ]
}

webpack-bundle-analyzer

打包分析工具,通过 `yarn add -D webpack-bundle-analyzer`安装,基本配置如下:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const config = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      analyzerHost: '127.0.0.1',
      analyzerPort: 8888,
    }),
  ]
}

SourceMapDevToolPlugin

本插件实现了对 source map 生成内容进行更细粒度的控制。它也可以根据 devtool 配置选项的某些设置来自动启用。

module.exports = (conf) => {
  console.log('conf', conf, process.env.NODE_ENV)
  if (isProduction) {
    config.mode = 'production';

    config.plugins.push(new webpack.SourceMapDevToolPlugin({
      test: /\.(tsx|jsx|js)$/,
      filename: '[file].map',
      publicPath: '/',
    }));
  } else {
    config.mode = 'development';
  }
  return config;
}

compression-webpack-plugin

gzip压缩静态资源

const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  plugins: [new CompressionPlugin()],
};

loader

ts-loader

TypeScript 转化为 JavaScript,通过 yarn add -D ts-loader 安装,基本配置如下:

const config = {
  module: {
    rules: [
      {
        // test: /\.ts(x?)$/,
        test: /\.(ts|tsx)$/i,
        use: [{
          loader: 'ts-loader',
          options: {
            // 跳过ts类型检查
            transpileOnly: true,
          },
        }],
        exclude: ['/node_modules/'],
      },
    ]
  }
}

esbuild-loader

esbuild-loader 是一个构建在 esbuild 上的 webpack loader,且可以替代 babel-loaderts-loader 来提高构建速度

const config  = {
  module: {
    rules: [
      {
        // Match js, jsx, ts & tsx files
        test: /\.[jt]sx?$/,
        loader: 'esbuild-loader',
        options: {
          // JavaScript version to compile to
          target: 'es2015'
        }
      },
    ],
  },
}

esbuild-loader 可替换 TerserPlugin 和 CssMinimizerPlugin

const { EsbuildPlugin } = require('esbuild-loader')

const config = {
  optimization: {
    minimizer: [
      new EsbuildPlugin({
        target: 'es2015'  // Syntax to compile to (see options below for possible values)
      })
    ]
  },
}

css

  • style-loader 将js文件中引入的css插入到html模板文件

  • mini-css-extract-pluginstyle-loader 功能一样,只是打包后会单独生成 css 文件而非直接写在 html 文件中,用于生产环境,开发环境不需要另外生成文件

  • css-loader 让js文件可以通过 importrequire 等命令导入css代码

  • sass-loader 将sass代码转换为css代码

  • less-loader 将less代码转换为css代码

  • postcss-loader 处理css代码,因为是处理css,所以postcss-loader要放在sass-loader等之后,可以在 webpack.config.js 中直接进行配置,或在 postcss.config.js 中进行配置,postcss-loader会自动加载该配置文件。

  • postcss-preset-env 将最新的css语法转换为目标环境浏览器能够理解的语法,新版本已内置autoprefixer功能

postcss.config.js

module.exports = {
  plugins: [
    [
      "postcss-preset-env",
      {
        // Options
      },
    ],
  ],
};

通过下面命令批量安装所需的css相关loader

yarn add -D style-loader css-loader sass-loader less-loader postcss-loader postcss-preset-env postcss sass

css 相关loader配置如下:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';

const config = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
      },
      {
        test: /\.less$/,
        use: [stylesHandler, 'css-loader', 'postcss-loader',
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                modifyVars: {
                  'primary-color': '#0080FF',
                },
                javascriptEnabled: true,
                math: 'always',
              },
            },
          },
        ],
      },
      {
        test: /\.css$/i,
        use: [stylesHandler, 'css-loader', 'postcss-loader'],
      },
    ]
  }
}

在 webpack 5 之前,通常使用:

  • raw-loader 将文件导入为字符串

  • url-loader 将文件作为 data URI 内联到 bundle 中

  • file-loader 将文件发送到输出目录

const config = {
  module: {
    rules: [
      {
        test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
        type: 'asset',
      },
    ]
  }
}

cache

缓存生成的 webpack 模块和 chunk,来改善构建速度。cache 会在开发 模式被设置成 type: 'memory' 而且在 生产 模式 中被禁用。

const config = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      // This makes all dependencies of this file - build dependencies
      config: [__filename],
      // 默认情况下 webpack 与 loader 是构建依赖。
    },
  }
}

externals

externals 配置项提供了阻止将某些 import 的包(package)打包到 bundle 中的功能,在运行时(runtime)再从外部获取这些扩展依赖(external dependencies)

编码风格

EditorConfig

root = true

[*] # 匹配全部文件
charset = utf-8 # 设置字符集
indent_style = space # 缩进风格,可选 space、tab
indent_size = 2 # 缩进的空格数
end_of_line = lf # 结尾换行符,可选 lf、cr、crlf
insert_final_newline = true # 在文件结尾插入新行
trim_trailing_whitespace = true # 删除一行中的前后空格

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

Prettier

代码格式化工具,目前总共有23个配置项。通过 yarn add -D prettier 安装

在项目根目录下新建 .prettierrc.js

module.exports = {
  // 每行代码长度 默认80
  printWidth: 100,
  // 每个tab相当于多少个空格 默认2
  tabWidth: 2,
  // 是否使用tab进行缩进 默认false
  useTabs: false,
  // 声明结尾使用分号 默认true
  semi: true,
  // 使用单引号 默认false
  singleQuote: true,
  /**
   * 对象属性的引号使用
   *
   * "as-needed" 仅在需要时在对象属性周围添加引号。
   * "consistent" 如果对象中至少有一个属性需要引号,请引用所有属性。
   * "preserve" 保留用户输入的情况
   */
  quoteProps: 'as-needed',
  // 在JSX中使用单引号而不是双引号,默认值为false
  jsxSingleQuote: true,
  // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
  trailingComma: 'es5',
  // 字面量对象括号中的空格,默认值为true
  bracketSpacing: true,
  /**
   * 多行JSX中的 > 放置在最后一行的结尾,而不是另起一行 默认值为false
   *
   * false:
   * <button
   *   className="prettier-class”
   *   id="prettier-id”
   *   onClick={this.handleClick}
   * >
   *   Click Here
   * </button>
   *
   * true:
   * <button
   *   className="prettier-class”
   *   id="prettier-id”
   *   onClick={this.handleClick}>
   *   Click Here
   * </button>
   */
  jsxBracketSameLine: false,
  /**
   * 箭头函数参数括号 默认avoid 可选 avoid always
   * avoid 能省略括号的时候就省略 例如x => x
   * always 总是有括号
   */
  arrowParens: 'avoid',
  // 格式化文件中某一段代码,默认格式化整个文件
  rangeStart: 0,
  rangeEnd: Infinity,
  // 格式化的解析器,默认值为babylon(until v1.13.0)
  parser: 'babylon',
  /**
   * 指定要使用的文件名,以推断要使用哪个解析器。
   * 该选项仅在CLI和API中有用。在配置文件中使用它没有意义。
   */
  filepath: '',
  /**
   * Prettier可以限制自己只格式化在文件顶部包含特殊注释(称为pragma)的文件。
   * 这在逐渐将大型、未格式化的代码库转换为更漂亮的代码库时非常有用。
   *
   * @prettier
   */
  requirePragma: false,

  /**
   * Prettier可以在文件顶部插入一个特殊的@format标记,指定该文件已使用Prettier进行格式化。
   * 当与——require-pragma选项一起使用时,效果很好。
   * 如果在文件的顶部已经有一个文档块,那么这个选项将添加一个带有@format标记的换行符。
   *
   * @prettier
   */
  insertPragma: false,
  /**
   * "always" - Wrap prose if it exceeds the print width.
   * "never" - Un-wrap each block of prose into one line.
   * "preserve" - Do nothing, leave prose as-is. First available in v1.9.0
   */
  proseWrap: 'preserve',

  /**
   * "css" - 遵守CSS display 属性的默认值
   * "strict"  - 空格被认为是敏感的
   * "ignore" - 空格被认为是不敏感的
   */
  htmlWhitespaceSensitivity: 'ignore',
  // 是否缩进Vue文件中的脚本和样式标签 默认值为false
  vueIndentScriptAndStyle: false,
  /**
   * 设置统一的行结尾样式(适用于v1.15.0+) 默认值为lf
   *
   * "lf" – 仅换行(\ n),在Linux和macOS以及git repos内部通用
   * "crlf" - 回车符+换行符(\ r \ n),在Windows上很常见
   * "cr" - 仅回车符(\ r),很少使用
   * "auto" - 保持现有的行尾(通过查看第一行后的内容对一个文件中的混合值进行归一化)
   */
  endOfLine: 'lf',
  /**
   * 控制Prettier是否格式化文件中嵌入的引用代码。
   *
   * "auto" - 如果pretty可以自动识别,则格式化嵌入的代码。
   * "off" - 永远不要自动格式化嵌入代码。
   */
  embeddedLanguageFormatting: 'auto',
  // 在HTML、Vue和JSX中是否强制每行使用单个属性。默认值为false
  singleAttributePerLine: false
};

如果配置了 prettier 注意配置规则与eslint保持一致,或者仅配置eslint规则即可。

.browserslistrc

在不同的前端工具之间共用目标浏览器和 Node 版本的配置文件。它主要被以下工具使用:AutoprefixerBabelpost-preset-enveslint-plugin-compatstylelint-unsupported-browser-featurespostcss-normalize

package.json 中配置:

{
  "browserslist": [
    "> 0.25%",
    "not dead",
    "ie 10",
    "chrome 45",
    "ios 9",
    "android 4.4",
  ]
}

.browserslistrc 中配置:

# 默认值

> 0.5%
last 2 versions
Firefox ESR
not dead

eslint (以配置airbnb规则为例)

# 快速初始化一个eslint配置
npm init @eslint/config

安装airbnb基础规则

npx install-peerdeps --dev eslint-config-airbnb
# 等同于运行下面命令
yarn add [email protected] eslint@^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react@^7.28.0 eslint-plugin-react-hooks@^4.3.0 --dev

安装airbnb-typescript规则

yarn add eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser --dev
  • eslint

  • eslint-config-airbnb

  • eslint-plugin-import

  • eslint-plugin-jsx-a11y

  • eslint-plugin-react

  • eslint-plugin-react-hooks

  • @typescript-eslint/eslint-plugin

  • @typescript-eslint/parser

在项目根目录下新建 .eslintrc.cjs 文件

module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  env: { browser: true, es2020: true, node: true, },
  parser: '@typescript-eslint/parser',
  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
  plugins: ['react-refresh'],
  rules: {
    // 使用2个空格缩进
    'indent': ['error', 2, { SwitchCase: 1 }],
  },
}

当保存代码时,按照eslint设置的规则自动修复代码,配置如下:

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
}