最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React + Typescrpit + Webpack搭建项目

    正文概述 掘金(Ego_Tao)   2021-02-21   958

    React + Typescrpit + Webpack搭建项目

    写在前面

    很早就想从npm init开始纯手工搭个React项目(以前常常都是直接脚手架或者ctrl+C ctrl+V前人搭好的改改就继续用了),刚好最近工作上要写个demo,就想着趁机来操作(摸鱼)一手,顺便记录一下。
    尽管现在各种脚手架已经很多很方便了,诸如vue-cli、create-react-app之类官方的脚手架,亦或者dva、umi之类的大厂出品的一整套解决方案。我依然认为有必要手动搭建一下,了解一下一个前端项目中到底用了哪些东西,各个库都起了什么作用。这样在遇到一些问题的时候,也能更好的定位问题。
    只是记录搭建一个React项目的过程的学习笔记,不是最佳实践或者项目模板之类的

    相关技术栈:

    所有相关依赖都是直接安装的最新的(2021.2.20),不同的版本会有一些不同的用法。最后会贴出package.json

    项目结构

    .
    ├── README.md
    ├── config  // 配置文件
    │   ├── default.config.json
    │   ├── development.config.json
    │   └── production.config.json
    ├── global.d.ts // ts全局声明
    ├── mock  // mock
    │   └── index.js
    ├── package-lock.json
    ├── package.json
    ├── prettier.config.js  // prettier配置
    ├── src
    │   ├── common  // 公共内容
    │   │   ├── assets  // 图片、字体等静态资源
    │   │   ├── styles.css  // 公共样式
    │   │   └── type.ts	// 公共ts
    │   ├── components  // 公共组件
    │   ├── fetch // 接口相关
    │   ├── index.ejs // 入口html模板
    │   ├── index.tsx // 入口文件
    │   ├── lib // 外部依赖
    │   ├── pages // 页面
    │   ├── redux // redux
    │   ├── root.less // 根节点样式
    │   ├── root.tsx  // 根节点
    │   ├── routes  // 路由
    │   └── utils // 工具库
    ├── tsconfig.json // ts配置
    ├── webpack  // webpack配置
    │   ├── webpack.common.js
    │   ├── webpack.dev.js
    │   └── webpack.prod.js
    ├── .eslintrc.js  // eslint配置
    ├── .stylelintrc.json  // stylelint配置
    ├── .gitignore
    └── webpack.config.js // webpack配置入口
    

    依赖安装

    前置工作: 创建一个项目目录,npm init。这里

    React、Typescript、Webpack安装

    # React相关
    npm i react react-dom react-router-dom react-redux @reduxjs/toolkit redux-saga
    # ts
    npm i typescript --save-dev
    # webpack
    npm i webpack webpack-cli --save-dev
    

    Webpack插件安装

    # 控制台输出美化,不影响项目运行
    npm i progress-bar-webpack-plugin friendly-errors-webpack-plugin --save-dev 
    # 移除之前打包生成的文件
    npm i clean-webpack-plugin --save-dev
    # 处理html
    npm i html-webpack-plugin --save-dev
    # tsconfig-path的webpack插件 主要是解决ts路径的问题
    npm i tsconfig-paths-webpack-plugin --save-dev
    # webpack配置合并,在这里主要是为了合并公共配置到不同环境中
    npm i webpack-merge --save-dev
    # gzip的webpack插件
    npm i compression-webpack-plugin --save-dev
    # bundle分析,可以看到打包后各个bundle的体积之类的信息,方便对打包进行优化
    npm i webpack-bundle-analyzer --save-dev
    # dev server
    npm i webpack-dev-server --save-dev
    

    安装types

    各个库的ts支持。

    npm i @types/react @types/react-dom @types/react-redux @types/react-router-dom @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
    

    安装babel

    # babel核心、babel-react、babel-typescript
    npm i @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --save-dev
    npm i @babel/plugin-transform-runtime --save-dev
    # 模块处理插件
    npm i babel-plugin-module-resolver babel-plugin-import --save-dev
    # 会用到的一些babel plugin
    # 编译class属性的插件  这里主要解决react class组件里方法需要bind(this)的问题,如果写function component不用也可以
    npm i @babel/plugin-proposal-class-properties --save-dev
    # 支持装饰器,如果不需要就不用了
    npm i @babel/plugin-proposal-decorators --save-dev
    # 支持 a?.b这种语法
    npm i @babel/plugin-proposal-optional-chaining --save-dev
    # 动态导入
    npm i @babel/plugin-syntax-dynamic-import --save-dev
    

    安装loader

    npm i babel-loader css-loader file-loader style-loader url-loader --save-dev
    

    安装less

    npm i less less-loader --save-dev
    

    安装postcss

    npm i postcss-loader postcss-import-sync2 postcss-less postcss-preset-env postcss-pxtorem --save-dev
    npm i autoprefixer --save-dev
    

    lint相关

    主要用来规范和格式化代码。

    # eslint
    npm i eslint eslint-plugin-import eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
    # stylelint
    npm i stylelint stylelint-config-prettier stylelint-config-standard --save-dev
    # prettier
    npm i prettier --save-dev --save-exact
    

    其它

    # 设置node变量,可以用来区分执行环境
    npm i cross-env --save-dev
    # 根据需要自行调整 这里列一些比较常用的
    npm i axios classnames dayjs
    

    Webpack配置

    webpack.config.js

    const devConfig = require('./webpack/webpack.dev');
    const prodConfig = require('./webpack/webpack.prod');
    // 根据环境加载不同配置
    module.exports = process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
    

    webpack.common.js

    const path = require('path');
    const ProgressBarPlugin = require('progress-bar-webpack-plugin');
    const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
    const webpack = require('webpack');
    const autoprefixer = require('autoprefixer');
    const publicPath = process.env.NODE_ENV === 'production' ? '' : '/';
    const SCOPE_NAME = '[path][name]__[local]';
    module.exports = {
      entry: path.resolve(__dirname, '../src/index.tsx'),
      target: 'web',
      resolve: {
        modules: [path.resolve(__dirname, '../'), 'node_modules'],
        alias: {},
        extensions: ['.tsx', '.ts', '.js', '.less', '.css'],
        symlinks: false,
        cacheWithContext: false,
        plugins: [
          new TsconfigPathsPlugin({
            configFile: path.resolve(__dirname, '../tsconfig.json'),
          }),
        ],
        fallback: {
          util: false,
        },
      },
      module: {
        rules: [
        	// babel
          {
            test: /\.(ts|tsx|js|jsx)?$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  sourceType: 'unambiguous',
                  presets: [
                    '@babel/preset-env',
                    '@babel/preset-react',	// 编译react
                    '@babel/preset-typescript',	// 编译ts
                  ],
                  plugins: [
                    [
                      'module-resolver',	// 模块处理
                      {
                        extensions: ['.js', '.jsx', '.ts', '.tsx', '.less', '.css'],	// 自动填充后缀,例如写'./index' 就会按着数组顺序去路径下找对应的文件
                        alias: {},	// 这里可以申明一些路径别名
                      },
                    ],
                    ['@babel/plugin-transform-runtime'],
                    ['@babel/plugin-proposal-class-properties', { loose: true }],
                    [
                      '@babel/plugin-proposal-decorators', // 支持装饰器
                      {
                        legacy: true,
                      },
                    ],
                    '@babel/plugin-syntax-dynamic-import', // 动态导入
                    '@babel/plugin-proposal-optional-chaining', // 这两个是用来处理 a && a.b => a.?b的 避免多层对象写的过于复杂
                  ],
                },
              },
            ],
          },
          // 处理css
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader',
              'postcss-loader',
              // 最新的(5.0.0)postcss需要单独写config文件,下面这种写法会报错,可以使用(v3.0.0)
              // {
              //   loader: 'postcss-loader',
              //   options: {
              //     plugins: () => [autoprefixer()],  // 浏览器兼容性前缀补全
              //   },
              // },
            ],
          },
          // 处理less
          {
            test: /\.less$/,
            exclude: /node_modules/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: {
                    localIdentName: SCOPE_NAME,
                  },
                  importLoaders: 3,
                  sourceMap: false,
                },
              },
              'postcss-loader',
              // 最新的(5.0.0)postcss需要单独写config文件,下面这种写法会报错,可以使用(v3.0.0)
              // {
              //   loader: 'postcss-loader',
              //   options: {
              //     plugins: [autoprefixer()],
              //   },
              // },
              {
                loader: 'less-loader',
              },
            ],
          },
          // 图片处理
          {
            test: /\.(jpe?g|png|gif|svg)$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  esModule: false, // 不然src会变成[object%20Module]
                  emitFile: true,
                  limit: 3 * 1024,
                  name: 'images/[name]__[hash:5].[ext]',
                  publicPath: publicPath,
                },
              },
            ],
          },
          // 处理文件
          {
            test: /\.(ttf|mp3|mp4)$/,	// 这里只写了字体/mp3/mp4 需要自己添加正则就好
            exclude: /node_modules/,
            use: [
              {
                loader: 'file-loader',
                options: {
                  name: 'assets/[name]__[hash:5].[ext]',
                  publicPath: publicPath,
                },
              },
            ],
          },
        ],
      },
      plugins: [
        new ProgressBarPlugin(), // 进度条
        new FriendlyErrorsWebpackPlugin(),  // 报错信息
        new CleanWebpackPlugin({  // 清除上次编译的内容
          verbose: true, // Write logs to console.
          dry: false,
        }),
      ],
    };
    

    webpack.dev.js

    const path = require('path');
    const { merge } = require('webpack-merge');
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    const common = require('./webpack.common');
    // 根据环境做一些不同的配置
    // const env = process.env.NODE_ENV;
    // const config = require(`../config/${env}.config.json`);
    module.exports = merge(common, {
      mode: 'development',
      output: {
        publicPath: '/',
        path: path.resolve(__dirname, '../dist'),
      },
      devServer: {
        contentBase: path.join(__dirname, '../dist'),
        compress: true,
        host: '0.0.0.0',
        port: 3000,
        hot: true,
        proxy: {
          // 代理
          // '/': {
          //   target: config.backend,
          // },
        },
      },
      devtool: 'eval-cheap-module-source-map',
      plugins: [
        new HTMLWebpackPlugin({
          cache: false,
          filename: 'index.html',
          template: path.resolve(__dirname, '../src/index.ejs'),
          favicon: path.resolve(__dirname, '../src/common/assets/favicon.ico'),
        }),
      ],
    });
    
    

    webpack.prod.js

    const path = require('path');
    const { merge } = require('webpack-merge');
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    const TerserPlugin = require('terser-webpack-plugin');
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    const CompressionPlugin = require('compression-webpack-plugin');
    const common = require('./webpack.common');
    
    const vendors = [
      'react',
      'react-dom',
      'react-router-dom',
      '@reduxjs/toolkit',
      'react-redux',
      'axios',
      'redux-saga',
    ];
    module.exports = merge(common, {
      entry: {
        vendor: vendors,
        index: {
          import: path.resolve(__dirname, '../src/index.tsx'),
          dependOn: 'vendor',
        },
      },
      mode: 'production',
      output: {
        filename: 'js/[contenthash].js',
        chunkFilename: 'chunk/[chunkhash].js',
        path: path.resolve(__dirname, '../dist'),
      },
      plugins: [
        // 针对html文件压缩 聊胜于无
        new HTMLWebpackPlugin({
          cache: false,
          filename: 'index.html',
          template: path.resolve(__dirname, '../src/index.ejs'),
          minify: {
            collapseWhitespace: true, // 折叠空白区域
            preserveLineBreaks: false,
            minifyCSS: true, // 压缩文内css
            minifyJS: true, // 压缩文内js
            removeComments: true, // 移除注释
          },
        }),
        // 启动gzip
        new CompressionPlugin({
          test: /.js$/, // 还可以扩展其他文件类型
        }),
        // 打包分析 如果需要的时候移除注释就好了
        // new BundleAnalyzerPlugin(),
      ],
      optimization: {
        splitChunks: {
          chunks: 'all',
        },
        minimize: true,
        minimizer: [
          new TerserPlugin(),
        ],
      },
    });
    
    

    postcss.config.js

    // 如果使用的是最新的postcss
    // 在webpack里直接写options 或者 postcssOptions好像不行(也可能是我写错了)还没有仔细研究 下面是官方写法
    module.exports = {
      plugins: [
        require('autoprefixer'),
      ],
    };
    
    

    到这里就完成了基本的webpack配置。还可以根据项目需要如移动端可以补充px2rem之类的插件;需要antd可以针对antd做一些vender、或者主题less处理;针对编译过程、打包结果进行优化等等。这里就不扩展了。

    TypeScript

    可以直接通过tsc --init 生成一个tsconfig.json。再根据需要自己调整。 tsconfig.json的部分内容

    {
      ...其他的一些配置
      "files": [
        "./global.d.ts" // 申明一个全局的.d.ts
      ],
    }
    

    ./global.d.ts

    // 这边申明了less文件 之后使用import styles from 'xxx.less'就不会有ts报错了
    declare module '*.less' {
      const content:{[className:string]:string};
      export default content;
    }
    // 如果使用的第三方库没有ts,可以自己在这边申明
    
    

    代码

    入口

    这里也可以直接使用html或者其他模板语言 index.ejs

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>demo</title>
    </head>
    <body>
      <div id="root"></div>
    </body>
    </html>
    

    index.tsx

    import React from 'react';
    import * as ReactDOM from 'react-dom';
    import { Root } from './root';
    ReactDOM.render(
      <Root/>,
      document.getElementById('root'),
    );
    
    

    我习惯把入口和root(React代码结构的根节点)分开来写。 root.tsx

    import React from 'react';
    import { Provider } from 'react-redux';
    import { HashRouter } from 'react-router-dom';  // router模式 自行选择 这里拿hash做例子
    import { getStore } from './redux/root-store';  // 获取redux store实例的方法
    import './common/styles.css'; // 公共css 一般用来写清楚浏览器样式之类的内容
    import { Routes } from './routes';  // router
    /**
    redux store的实例,这里导出是方便一些不在context里的组件或者一些方法使用redux里的数据
    注意,这些不在context里的组件是不会因为store的变化重新渲染的
    */
    export const store = getStore();
    export const Root:React.FC = React.memo(function Root() {
      // 启用react的严格模式 具体可以去react的官网了解一下
      return (
        <React.StrictMode>
          <Provider store={store}>
            <HashRouter>
              <Routes/>
            </HashRouter>
          </Provider>
        </React.StrictMode>
      );
    });
    
    

    redux

    这里想提一嘴,忘记在官网还是哪里看到的,如果你不确定是否需要用redux,就不要用。对于复杂度不高的项目(比较直接的就是不需要多组件共享数据之类的情况),redux只会让代码变复杂,没什么帮助。如果要使用,也一定要想清楚那些数据需要redux进行管理。维护过一个啥啥啥都放在redux里的项目,简直是噩梦。
    redux这里使用了ReduxToolKit,可以去官网了解一下,比起手写redux能少些不少代码,而且对ts支持也好很多。 异步中间件使用redux-saga,不了解的同学也可以去官网了解一下。可以根据需要自己选择中间件,比如redux-thunk之类的。 root-store.ts

    import { configureStore } from '@reduxjs/toolkit';
    import createSagaMiddleware from 'redux-saga';
    import { rootReducer } from './root-reducer';
    import { rootSaga } from './root-saga';
    export function getStore() {
      const sagaMiddleWare = createSagaMiddleware();
      const store = configureStore({
        reducer: rootReducer,
        middleware: [sagaMiddleWare], // 可以使用多个中间件,往数组里补充对应的实例就行了
      });
      sagaMiddleWare.run(rootSaga);
      return store; // 这里返回store的实例
    }
    
    

    root-reducer.ts

    import { combineReducers } from '@reduxjs/toolkit';
    import { userSlice } from './user'; // user相关内容下面会写
    // 这个好像没啥说的,方法名很明确了,就是组合reducer
    export const rootReducer = combineReducers({
      user: userSlice.reducer,
    });
    // 这里返回了reducer的return type,便于其他地方使用
    export type RootState = Readonly<ReturnType<typeof rootReducer>>;
    
    

    root-saga.ts

    import { all, spawn, call } from 'redux-saga/effects';
    import { userSaga } from './user/saga'; // user相关内容下面会写
    // saga
    export function* rootSaga() {
      const sagas = [
        userSaga,
      ];
      try {
        yield all(
          sagas.map((saga) => spawn(function* () {
            while(true) {
              yield call(saga);
              break;
            }
          })),
        );
      } catch (err) {
        console.log('root saga error', err);
      }
    };
    
    

    再提供一个简单的demo,也就是上面user的内容 user.ts

    import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
    import { UserInfo, UserState } from './type'; // 定义一些ts 就不说了
    // 初始state
    const initialState:UserState = {
        userInfo: {
          name: '',
          avatarUrl: '',
        },
        token: '',
      };
    const REDUCER_NAME_SPACE = 'user';  // space name
    export const userSlice = createSlice({
      name: REDUCER_NAME_SPACE,
      initialState,
      // 简单写几个reducer
      reducers: {
        // 重置state,比如退出登录之类的时候执行
        initUserState(state:UserState):void {
          state = initialState;
        },
        setUserState(state:UserState, { payload }:PayloadAction<Partial<UserState>>):void {
          state = {
            ...state,
            ...payload,
          };
        },
      },
    });
    
    export const { initUserState, setUserState, setUserInfo } = userSlice.actions;
    // 异步action 这里拿登录做例子
    export const login = createAction<{username:string;password:string}>(`${REDUCER_NAME_SPACE}/login`);
    
    

    /user/saga.ts

    import message from 'antd/lib/message';
    import { take, call, fork } from 'redux-saga/effects';  // saga相关知识就先不扩展了
    import { fetchLogin } from '../../fetch/http';
    import { login } from './index';
    
    // saga的一个核心就是generator 不了解的同学可以去看看es6里相关内容
    function* watchLogin() {
      while(true) {
        try {
          // 获取action的payload
          const { payload } = yield take(login);
          const { username, password } = payload;
          // 执行请求
          const res = yield call(fetchLogin, username, password);
          // 一些业务逻辑
          if (res.data) {
            history.push('/home');
          } else {
            // toast、tip之类的
          }
        } catch (err) {
          console.log('login error', err);
          alert(err.message);
        }
      }
    }
    
    export function* userSaga() {
      yield fork(watchLogin);
    }
    
    

    router

    index.tsx

    import React, { Suspense } from 'react';
    import { RouteProps, Route, Switch } from 'react-router-dom';
    const routes:RouteProps[] = [
      {
        path: '/login',
        exact: true,
        // 按需加载,申明chunkName是方便看,比如打包的时候可以直接使用chunkName(会有缓存问题,还是用chunkhash比较好,具体还是看业务场景)
        // 使用import的话只能用export default哦
        component: React.lazy(() => import(/* webpackChunkName:"LoginPage" */ '../pages/login')),
      },
      {
        path: '/',
        component: React.lazy(() => import(/* webpackChunkName:"HomePage" */ '../pages/index')),
      },
    ];
    export const Routes = () => (
      // 可以做一个loading组件
      <Suspense fallback={<div>loading</div>} >
        <Switch>
          {
            routes.map((val, key) => (
              <Route
                key={`route_${key}`}
                {...val}/>
            ))
          }
        </Switch>
      </Suspense>
    );
    
    

    页面

    这里简单写个index。
    pages/index/index.tsx

    import React from 'react';
    import styles from './index.less';  // 我这边直接使用的className,如果要用styleName的话,需要加react-css-module相关loader和插件
    const IndexPage:React.FC = () => (
      <div className={styles.container}>这里是首页</div>
    );
    export default React.memo(IndexPage);
    
    

    运行

    到这里项目基本上就搭好啦,接下来就运行啦。
    package.json

    {
      "scripts": {
        "start": "cross-env NODE_ENV=development webpack serve",
        "build": "cross-env NODE_ENV=production webpack"
      }
    }
    

    在package.json里增加上面的内容,然后执行npm run start就可以啦。
    最后贴出package.json,如果运行异常请确认依赖版本。 不同版本用法会有很大区别,如: React 16开始支持hooks等特性;webpack4, 5配置上也会有一些改动;等等,如果跑不起来可以根据版本做一些调整。

    {
      "name": "demo",
      "version": "1.0.0",
      "description": "基于React + typescript + webpack搭建的项目",
      "main": "index.js",
      "scripts": {
        "start": "cross-env NODE_ENV=development webpack serve",
        "build": "cross-env NODE_ENV=production webpack"
      },
      "keywords": [
        "React",
        "Typescript"
      ],
      "author": "Ego Tao",
      "license": "ISC",
      "dependencies": {
        "@reduxjs/toolkit": "^1.5.0",
        "axios": "^0.21.1",
        "classnames": "^2.2.6",
        "dayjs": "^1.10.4",
        "react": "^17.0.1",
        "react-dom": "^17.0.1",
        "react-redux": "^7.2.2",
        "react-router-dom": "^5.2.0",
        "redux-saga": "^1.1.3"
      },
      "devDependencies": {
        "@babel/core": "^7.12.17",
        "@babel/plugin-proposal-class-properties": "^7.12.13",
        "@babel/plugin-proposal-decorators": "^7.12.13",
        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13",
        "@babel/plugin-proposal-optional-chaining": "^7.12.17",
        "@babel/plugin-syntax-dynamic-import": "^7.8.3",
        "@babel/plugin-transform-runtime": "^7.12.17",
        "@babel/preset-env": "^7.12.17",
        "@babel/preset-react": "^7.12.13",
        "@babel/preset-typescript": "^7.12.17",
        "@types/react": "^17.0.2",
        "@types/react-dom": "^17.0.1",
        "@types/react-redux": "^7.1.16",
        "@types/react-router-dom": "^5.1.7",
        "@typescript-eslint/eslint-plugin": "^4.15.1",
        "@typescript-eslint/parser": "^4.15.1",
        "babel-loader": "^8.2.2",
        "babel-plugin-import": "^1.13.3",
        "babel-plugin-module-resolver": "^4.1.0",
        "clean-webpack-plugin": "^3.0.0",
        "compression-webpack-plugin": "^7.1.2",
        "css-loader": "^5.0.2",
        "eslint": "^7.20.0",
        "eslint-config-prettier": "^7.2.0",
        "eslint-plugin-import": "^2.22.1",
        "eslint-plugin-prettier": "^3.3.1",
        "eslint-plugin-promise": "^4.3.1",
        "eslint-plugin-react": "^7.22.0",
        "eslint-plugin-react-hooks": "^4.2.0",
        "file-loader": "^6.2.0",
        "friendly-errors-webpack-plugin": "^1.7.0",
        "html-webpack-plugin": "^5.1.0",
        "less": "^4.1.1",
        "less-loader": "^8.0.0",
        "postcss-import-sync2": "^1.1.0",
        "postcss-less": "^4.0.0",
        "postcss-loader": "^5.0.0",
        "postcss-preset-env": "^6.7.0",
        "postcss-pxtorem": "^5.1.1",
        "progress-bar-webpack-plugin": "^2.1.0",
        "style-loader": "^2.0.0",
        "stylelint": "^13.10.0",
        "stylelint-config-prettier": "^8.0.2",
        "stylelint-config-standard": "^20.0.0",
        "tsconfig-paths-webpack-plugin": "^3.3.0",
        "typescript": "^4.1.5",
        "url-loader": "^4.1.1",
        "webpack": "^5.23.0",
        "webpack-cli": "^4.5.0",
        "webpack-dev-server": "^3.11.2",
        "webpack-merge": "^5.7.3"
      }
    }
    
    

    总结

    写到这里突然发现好像并没有记录下来搭建的过程,更像是一开始就想好要用什么,直接搭建一样。?
    其实搭建过程可以分为这几个步骤:

    1. 安装React、TS、Webpack最基本的依赖
    2. 配置Webpack,先从entry、output之类的开始,然后通过webpack-dev-server跑起来
    3. 加入React和ts部分代码,报错,添加babel
    4. 去旧项目里看了一下还用了什么babel插件,挑了几个补进去(实际项目的话,可以遇到报错了再找对应的babel plugin处理)
    5. 增加支持css、less相关loader
    6. 添加url-loader、file-loader之类常用的loader并进行验证(还有个比较常用的就是worker-loader,不过这里没加)
    7. 基本功能都满足了,然后拆分webpack配置,分为dev和prod两种
    8. build,到这一步基本上就完成了,还随便去找了几个例如gzip之类的webpack插件
    9. 增加eslint、stylelint和prettier(配置是用的以前写的)

    本来还想写个啥dockerfile或者发布上传之类的脚本,又感觉没什么必要,毕竟本意是记录一个能跑起来的项目。

    顺便还踩了几个坑:

    1. 忘了装less,没有报缺失less而是webpack报了个比较奇怪的错(忘了截图)。然后去google了半天,都说是缺失文件。我找了半天没找到是啥原因。然后就直接去报错的地方打log,发现是.less文件的hash(具体规则不清楚,log的对象所有属性都是undefined)有问题。回去移除掉用了less的地方,果然就好了。然后才发现是少了less。直接裂开(我明明记得我装了,小声BB),可能是因为装了less-loader但是没有装less,所以webpack没有直接报处理不了.less文件。
    2. 依赖版本变化,版本变化导致一些用法变化。之前项目postcss用的3.0.0,这次装的5.0.0,我又是直接按以前项目里postcss写的。导致loader报错,webpack报错说这个loader没有options属性,但是有postcssOptions属性。用了postcssOptions,又说没有plugins属性。一怒(无能狂怒)之下,我就去了github去查最新的用法。

    算是写的第一篇比较完整的学习笔记,撒花。


    起源地下载网 » React + Typescrpit + Webpack搭建项目

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元