webpack5 纯手工动搭建React+TS项目

249 阅读4分钟

1. 前言

前段时间突发奇想,平时不是使用cra搭建项目就是使用其他三方模板, 都是基本上帮你配置好了所有的配置项,你只需要写业务代码就好了。但是回看一堆配置项时有时懵逼的,所以就想手动从0开始一步一步搭建一个基于webpack5的React Ts项目,以便于知道那些配置项是必须项,以及必须项都是为了完成什么任务,于是就有了下面的实践。

2. 初始化项目

项目目录结构就不多述了,对应的文件夹及文件无非就是 mkdir、touch的过程也不多说;

项目根路径下: 

npm init -y 

 初始化简单的package.json配置文件

3. 初始化typeScript配置

yarn add typescript ts-loader --dev

关于--dev也无需多说,并且我的理解ts、ts-loader 均是服务项目,对于前端项目来说浏览器只需要js,所以为了生成js文件的其他的配置项均是服务项目应该放在devDependencies下。

可以全局安装ts

yarn add typescript global

然后项目根路径下:初始化ts配置项 tsconfig.json

tsc --init


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

4.配置webpack相关

yarn add webpack webpack-cli webpack-merge html-webpack-plugin clean-webpack-plugin --dev

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

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

  • webpack.base.js 存放webpack的基本配置

  • webpack.dev.js 存放webpack的开发环境配置

  • webpack.prod.js 存放webpack的生产环境配置

    // config/webpack.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.dev.js

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

/**
 * @type {import('webpack').WebpackOptionsNormalized}
 */
const devServer = {
  hot: true,  port: 3000,  host: '127.0.0.1',  compress: true,  open: true,  proxy: {    '/api': {      target: 'http://localhost:8000',      pathRewrite: { '^/api': '' },      secure: false    }  }}

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

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

//   config/webpack.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

yarn add 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.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

yarn add react react-dom react-router-dom
yarn add @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. 功能性配置

yarn add style-loader less-loader less css-loader postcss-loader postcss-normalize autoprefixer postcss-preset-env --dev

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

yarn add react-dev-utils resolve-url-loader --dev

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

这里后续我移除了sass,使用less,sass的依赖包安装总是失败

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

module.exports = {
    ...
    module: {
        rules: [
          ...
          {
            test: /\.(css|less)$/,        exclude: /\.module\.scss$/,        use: [isDev ? require.resolve('style-loader') : MiniCssExtractPlugin.loader, 
          'css-loader', {          loader: 'postcss-loader',          options: {            postcssOptions: {              plugins: [                require('postcss-preset-env')()              ],            },          }        }, "less-loader"]          },
          {
            test: /\.module\.scss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: {
                    getLocalIdent: getCSSModuleLocalIdent,
                  },
                },
              },
              'postcss-loader',
              'sass-loader',
            ],
          },
        ],
    },
};

图片地址解析

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.base.js

// webpack.base.js
const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const MiniCssExtractPlugin = require('mini-css-extract-plugin')/** * @type {import('webpack').Configuration} */const isDev = process.env.NODE_ENV === 'development'module.exports = {  target: 'web',  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|less)$/,        exclude: /\.module\.scss$/,        use: [isDev ? require.resolve('style-loader') : MiniCssExtractPlugin.loader, 'css-loader', {          loader: 'postcss-loader',          options: {            postcssOptions: {              plugins: [                require('postcss-preset-env')()              ],            },          }        }, "less-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, '../public/index.html'),      filename: 'index.html',    }),    new CleanWebpackPlugin(),    new MiniCssExtractPlugin({      filename: 'css/[name].[hash].css',    })  ],  cache: {    type: 'filesystem',    // 可选配置    buildDependencies: {      config: [__filename], // 当构建依赖的config文件(通过 require 依赖)内容发生变化时,缓存失效    },    name: 'development-cache',  },}

webpack.dev.js

HotModuleReplacementPlugin  这个是热更新,

const webpackMerge = require('webpack-merge')const baseConfig = require('./webpack.base')const webpack = require('webpack');//引入webpack // const path = require('path')/** * @type {import('webpack').WebpackOptionsNormalized} */const devServer = {  hot: true,  port: 3000,  host: '127.0.0.1',  compress: true,  open: true,  proxy: {    '/api': {      target: 'http://localhost:8000',      pathRewrite: { '^/api': '' },      secure: false    }  }}const devConfig = {  mode: 'development',  devServer: devServer,  plugins: [//配置插件的节点    new webpack.HotModuleReplacementPlugin() //new 一个热更新的模块对象  ]}module.exports = webpackMerge.merge(baseConfig, devConfig)

webpack.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)

webpack 配置项很多,功能也是很强大,我目前只是基本的的一个可用项目搭建,后续还会随着学习过程逐步补充

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

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

yarn add mini-css-extract-plugin --dev

修改webpack.base.js 中的配置

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

11. 配置开发服务器

安装依赖

yarn add webpack-dev-server --dev

package.json 中添加启动命令

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

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

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

新建declaration.d.ts

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

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

需要使用到一个插件

yarn add typescript-plugin-css-modules --dev

配置tsconfig.json

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

这样,一个基本的项目后就已经搭建成了,当然想全部搞懂确实也需要下功夫。