创建 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'
}
}
复制代码