webpack5 手动搭建React TS项目

3,790 阅读5分钟

webpack5 手动搭建React TS项目

1. 前言

前段时间突发奇想,使用React这么长时间了,不是使用cra搭建项目就是使用antdpro, 都是基本上帮你配置好了所有的配置项,你只需要写业务代码就好了。所以闲下来的时间打算手动从0开始一步一步搭建一个基于webpack5的React Ts项目,于是就有了下面的实践,由于本人小白一名,所以有做得不对的地方欢迎大佬指正。

2. 初始化项目

找一个空白的目录然后执行下面代码:

mkdir webpack5-react-ts-tempalte
cd webpack5-react-ts-tempalte
npm init -y

3. 初始化typeScript配置

npm install --save-dev typescript ts-loader

新建一个tsconfig.json文件,我这里直接采用的是webpack官网的tsconfig配置, 可以通过查看TypeScript官方文档了解更多功能。

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true
  }
}

4.配置webpack相关

npm i webpack webpack-cli webpack-merge html-webpack-plugin clean-webpack-plugin --dev

由于我们的项目基本上都是分为开发环境和生产环境的,所以对于webpack的配置,个人新建了三个文件来存放不同的配置。

新建config 文件来存放webpack的配置文件:

  • webpack.config.base.js 存放webpack的基本配置
  • webpack.config.dev.js 存放webpack的开发环境配置
  • webpack.config.prod.js 存放webpack的生产环境配置
//  config/webpack.config.base.js
const path = require('path
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
/**
 * @type {import('webpack').Configuration}
 */

module.exports = {
  entry: {
    app: './src/index.tsx',
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].[hash].js',
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '管理后台',
      template: path.resolve(__dirname, '../index.html'),
      filename: 'index.html',
    }),
    new CleanWebpackPlugin(),
  ],
}

合并配置项

由于我们是分文件配置webpack,所以就会存在合并配置项这个操作。上面安装的webpack-merge 就是实现这个功能的插件。

//   config/webpack.config.dev.js

const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const path = require('path')

/**
 * @type {import('webpack').WebpackOptionsNormalized}
 */
const devServer = {
  port: 3000,
  host: 'localhost',
  contentBase: path.join(__dirname, '../publich'),
  watchContentBase: true,
  publicPath: '/',
  compress: true,
  historyApiFallback: true,
  hot: true,
  clientLogLevel: 'error',
  // open: true,
  watchOptions: {
    ignored: /node_modules/,
  },
}

const devConfig = {
  mode: 'development',
  devServer: devServer,
}

module.exports = webpackMerge.merge(baseConfig, devConfig)

//   config/webpack.config.prod.js

const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
/**
 * @type {import('webpack').WebpackOptionsNormalized}
 */
const prodConfig = {
  mode: 'production',
}

module.exports = webpackMerge.merge(baseConfig, prodConfig)

5. 配置babel

安装依赖

npm i babel-loader babel-plugin-import @babel/cli @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --dev

在根目录新建.babelrc 文件

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

webpack.config.base.js中添加mode配置

module.exports = {
    ...
    module: {
        rules: [
              { test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
              { test: /\.(ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ },
        ],
    },
};

6. 配置React

安装依赖

npm i react react-dom react-router-dom
npm i @types/react @types/react-dom @types/react-router-dom --dev

新建html文件

根目录新建index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
</head>

<body>
  <div id="root"></div>
</body>

</html>

新建工程入口文件

新建src 目录下新建App.tsx index.tsx

// App.tsx

import React from 'react'

const App = () => {
  return <div>1234</div>
}

export default App

// index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

7. 功能性配置

样式文件解析

npm i style-loader sass-loader sass css-loader postcss-loader postcss-normalize autoprefixer postcss-preset-env -D

因为我们平时开发中不只是使用scss,也会使用到scss module,所以同时配置一下,安装依赖

npm i react-dev-utils resolve-url-loader -D

webpack.config.base.js中添加mode配置,


const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')

module.exports = {
    ...
    module: {
        rules: [
          ...
          {
            test: /\.(css|scss)$/,
            exclude: /\.module\.scss$/,
            use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
          },
          {
            test: /\.module\.scss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: {
                    getLocalIdent: getCSSModuleLocalIdent,
                  },
                },
              },
              'postcss-loader',
              'sass-loader',
            ],
          },
        ],
    },
};

新增postcss.config.js文件并配置

const postcssNormalize = require('postcss-normalize')

module.exports = {
  plugins: [
    [
      'postcss-preset-env',
      {
        autoprefixer: {
          flexbox: 'no-2009',
        },
        stage: 3,
      },
    ],
    postcssNormalize(),
    require('autoprefixer')({
      overrideBrowserslist: ['last 2 version', '>1%', 'ios 7'],
    }),
  ],
}

图片地址解析

webpack5 内置 assets 类型,我们不需要额外安装插件就可以进行图片等资源文件的解析,配置如下:

{
  test: /\.(jpe?g|png|gif|svg|woff|woff2|eot|ttf|otf)$/i,
  type: "asset/resource",
},

8. 性能优化

webpack5 引入了缓存来提高二次构建速度,我们只需要在 webpack 配置文件中加入如下代码即可开心缓存

cache: {
  type: 'filesystem',
  // 可选配置
  buildDependencies: {
    config: [__filename], // 当构建依赖的config文件(通过 require 依赖)内容发生变化时,缓存失效
  },
  name: 'development-cache',
},

9. 完整配置

webpack.config.base.js

// webpack.config.base.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
/**
 * @type {import('webpack').Configuration}
 */

module.exports = {
  entry: {
    app: './src/index.tsx',
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].[hash].js',
  },
  module: {
    rules: [
      { test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.(ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ },
      {
        test: /\.(css|scss)$/,
        exclude: /\.module\.scss$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      },
      {
        test: /\.module\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                getLocalIdent: getCSSModuleLocalIdent,
              },
            },
          },
          'postcss-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(jpe?g|png|gif|svg|woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.jsx'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '管理后台',
      template: path.resolve(__dirname, '../index.html'),
      filename: 'index.html',
    }),
    new CleanWebpackPlugin(),
  ],
  cache: {
    type: 'filesystem',
    // 可选配置
    buildDependencies: {
      config: [__filename], // 当构建依赖的config文件(通过 require 依赖)内容发生变化时,缓存失效
    },
    name: 'development-cache',
  },
}

webpack.config.dev.js

const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const path = require('path')

/**
 * @type {import('webpack').WebpackOptionsNormalized}
 */
const devServer = {
  port: 3000,
  host: 'localhost',
  contentBase: path.join(__dirname, '../publich'),
  watchContentBase: true,
  publicPath: '/',
  compress: true,
  historyApiFallback: true,
  hot: true,
  clientLogLevel: 'error',
  // open: true,
  watchOptions: {
    ignored: /node_modules/,
  },
}

const devConfig = {
  mode: 'development',
  devServer: devServer,
}

module.exports = webpackMerge.merge(baseConfig, devConfig)

webpack.config.prod.js

const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
/**
 * @type {import('webpack').WebpackOptionsNormalized}
 */

const prodConfig = {
  mode: 'production',
}

module.exports = webpackMerge.merge(baseConfig, prodConfig)

10. css提取成单独的打包文件

上述配置中的css是打包到js中的,所以如果想要把css单独打包出来,就需要做一下配置,安装依赖

npm install --save-dev mini-css-extract-plugin

修改webpack.config.base.js 中的配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
	...
  plugins: [new MiniCssExtractPlugin({
              filename: 'css/[name].[hash].css',
	})],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
}

11. 配置开发服务器

安装依赖

npm i webpack-dev-server --dev

package.json 中添加启动命令

"scripts": {
  
    "start": "webpack serve --config ./config/webpack.config.dev.js",
    "build": "webpack --mode=production --config ./config/webpack.config.prod.js"
  },

12. 问题解决

当完成上述的步骤的时候,当你运行的时候,可能会收到这么一个报错。

Cannot find module './index.module.scss' or its corresponding type declarations.

因为是ts的项目,所以这个时候需要进行声明文件的书写。

新建declaration.d.ts

declare module '*.scss' {
  const content: Record<string, string>
  export default content
}

13. 获取良好的css 代码提示

需要使用到一个插件

npm install -D typescript-plugin-css-modules

配置tsconfig.json

{ 
    "compilerOptions": 
	{ 
            "plugins": [{ "name": "typescript-plugin-css-modules" }]
	 }
}

如果你使用的是vscode,可以跟我一样配置一下

根目录新建.vscode文件夹,然后新建settings.json

在文件中写入

{
	"typescript.tsdk": "node_modules/typescript/lib",
	"typescript.enablePromptUseWorkspaceTsdk":true
}

从工作区设置TypeScript版本,从而读取tsconfig.json文件

Untitled.png

然后就可以获得良好的代码提示了