阅读 431

入门级Webpack React配置模板

创建 React 和 Vue 项目时可以使用官方推荐的脚手架 create-react-app 和 vue-cli,但是如果要自定义 Webpack 配置会稍有些麻烦。我以前在没有接触这两个脚手架之前都是自己搭建 Webpack 环境,这篇文章算是对搭建环境的总结,记录了搭建一个入门级 react 框架的步骤和代码。

这次我们要搭建的是 React+Typescript+SCSS 的 Webpack 环境,想要实现的功能点有:

  • React
  • Typescript
  • SCSS
  • ESLint

写作本文时(2021.06)使用的软件版本号为:

  • node:14.17.1
  • webpack:5.40.0
  • typescript:4.3.4

1. 基本配置

1.1 创建项目

使用 npm 命令创建 package.json 文件:

npm init
复制代码

根据需要填写内容并修改生成的文件,大致内容见附录-package.json

dev命令用于启动开发服务器,build命令用于构建生产环境代码。

1.2. Webpack

安装

安装 Webpack:

npm i -D webpack webpack-cli webpack-dev-server webpack-merge
复制代码

我们使用 webpack-dev-server 作为开发服务器,使用webpack serve命令启动。为了方便管理配置使用了 webpack-merge。

基本配置

创建webpack目录,在目录下创建share.js,这是开发环境和生产环境的公用配置。内容为见附录-webpack/share.js

入口文件配置为和输出目录配置为:

module.exports = {
  entry: {
    index: Path.resolve(__dirname, '../../src/index.tsx')
  },
  output: {
    path: Path.resolve(__dirname, '../../dist/static'),
    filename: 'index.[contenthash].js'
  },
  resolve: {
    extensions: ['.js', '.ts', '.tsx', '.json'],
    alias: {
      '@': Path.resolve(__dirname, '../../src')
    }
  }
}
复制代码

输出文件在文件名中添加了内容哈希值,来避免应缓存而更新不及时的问题。extensions 表示 webpack 搜索文件时需要自动尝试解析的后缀。alias 配置了路径别名,这样在业务代码中可以用@代表src目录,从而简化路径的写法。

为了解析 tsx 文件,安装并配置 babel-loader:

npm i -D babel-loader
复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        },
        exclude: /node_modules/
      }
    ]
  }
}
复制代码

为了解析 scss 文件,安装并配置 scss-loader 等加载器:

npm i -D sass-loader css-loader style-loader
复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  }
}
复制代码

为了将生成的 js 文件自动插入 html 中,使用了 html-webpack-plugin 插件:

const Path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: Path.resolve(__dirname, '../../src/index.html'),
      publicPath: './static',
      filename: '../index.html'
    })
  ]
}
复制代码

创建development.js,这是开发环境的配置,内容见附录-webpack/development.js

其中配置了开发服务器,具备热更新、自动打开浏览器的功能。为了调试方便,开启了源码映射。

const { merge } = require('webpack-merge')
const Share = require('./share')
const Path = require('path')

module.exports = merge(Share, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: Path.resolve(__dirname, '../../dist'),
    host: '0.0.0.0',
    useLocalIp: true,
    port: 9000,
    open: true,
    hot: true
  }
})
复制代码

创建production.js,这是生产环境的配置,内容见附录-webpack/production.js

2. 模块

2.1 Typescript

我们使用 Babel 来编译 Typescript。

安装 Babel:

npm i -D @babel/core @babel/preset-env @babel/preset-typescript @babel/preset-react babel-loader
复制代码

为了让 Babel 解析 tsx,配置 babel.config.js:

module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'],
  plugins: ['@babel/plugin-transform-runtime']
}
复制代码

安装 Typescript:

npm i -D typescript
复制代码

使用 tsc 命令生成 tsconfig.js:

npx tsc --init
复制代码

修改配置为附录-tsconfig.json

2.2 React

安装 react,react-dom:

npm i -D react react-dom
复制代码

2.3 SCSS

安装 sass:

npm i -D sass
复制代码

2.4 ESLint

安装 eslint:

npm i -D eslint
复制代码

运行命令初始化 eslint:

eslint --init
复制代码

根据需要选择特性,其中必须选择的有:

  • React
  • Typescript
  • Browser

为了让 eslint 适配配置文件的写法,需要在env中添加node

module.exports = {
  env: {
    node: true
  }
}
复制代码

附录

package.json

{
  "name": "webpack-react-template",
  "version": "1.0.0",
  "description": "Webpack React配置模板",
  "author": "WingsJ",
  "main": "src/index.ts",
  "scripts": {
    "dev": "webpack serve -c webpack/development.js",
    "build": "webpack -c webpack/production.js"
  },
  "devDependencies": {
    "@babel/core": "^7.14.6",
    "@babel/plugin-transform-runtime": "^7.14.5",
    "@babel/preset-env": "^7.14.7",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.14.5",
    "@types/react": "^17.0.13",
    "@types/react-dom": "^17.0.8",
    "@typescript-eslint/eslint-plugin": "^4.28.1",
    "@typescript-eslint/parser": "^4.28.1",
    "babel-loader": "^8.2.2",
    "core-js": "^3.15.2",
    "css-loader": "^5.2.6",
    "eslint": "^7.30.0",
    "eslint-plugin-react": "^7.24.0",
    "html-webpack-plugin": "^5.3.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "sass": "^1.35.1",
    "sass-loader": "^12.1.0",
    "style-loader": "^3.0.0",
    "typescript": "^4.3.4",
    "webpack": "^5.40.0",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2",
    "webpack-merge": "^5.8.0"
  }
}
复制代码

webpack/share.js

const Path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    index: Path.resolve(__dirname, '../../src/index.tsx')
  },
  output: {
    path: Path.resolve(__dirname, '../../dist/static'),
    filename: 'index.[contenthash].js'
  },
  resolve: {
    extensions: ['.js', '.ts', '.tsx', '.json'],
    alias: {
      '@': Path.resolve(__dirname, '../../src')
    }
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        },
        exclude: /node_modules/
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: Path.resolve(__dirname, '../../src/index.html'),
      publicPath: './static',
      filename: '../index.html'
    })
  ]
}
复制代码

webpack/development.js

const { merge } = require('webpack-merge')
const Share = require('./share')
const Path = require('path')

module.exports = merge(Share, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: Path.resolve(__dirname, '../../dist'),
    host: '0.0.0.0',
    useLocalIp: true,
    port: 9000,
    open: true,
    hot: true
  }
})
复制代码

webpack/production.js

const { merge } = require('webpack-merge')
const Share = require('./share')

module.exports = merge(Share, {
  mode: 'production'
})
复制代码

tsconfig.json

{
  "include": ["src"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "lib": ["esnext", "dom"],
    "jsx": "react",
    "strict": true,
    "moduleResolution": "node",
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"] // 路径别名
    },
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
复制代码

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false,
        useBuiltIns: 'usage',
        corejs: 3,
        targets: 'defaults'
      }
    ],
    '@babel/preset-typescript',
    '@babel/preset-react'
  ],
  plugins: ['@babel/plugin-transform-runtime']
}
复制代码

.eslintrc.js

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    ecmaVersion: 12,
    sourceType: 'module'
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    indent: ['warn', 2],
    'linebreak-style': ['warn', 'unix'],
    quotes: ['warn', 'single'],
    semi: ['warn', 'never'],
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off'
  }
}
复制代码
文章分类
前端
文章标签