搭建自己的React+Typescript環境(一)

大門發表於2019-06-24

前言

前陣子在自己學習React,最開始上手使用的creat-react-app來建立自己的專案,2版本之後的create-react-app已經支援了很多功能,比如sass、資料mock、typescript支援等等,也升級了相關依賴babel、webpack到一個最新的版本,具體可以參照Create React App 中文文件,但是它將專案的webpack配置等內容給藏起來了,想要自己配置的話還要npm run eject才可見,不過對於我這種初學者已經足夠了,但是本著折騰的精神,在掘金看了好多大佬的配置文章,終於折騰出一個自己的專案模板,如果有什麼問題或者不對的地方,希望大佬們能及時指出,最後有專案地址~

第二篇生產開發環境配置已經寫完:搭建自己的React+Typescript環境(二)

專案簡介

主要的依賴以及版本

  • webpack4+
  • babel8+
  • typescript3.7+
  • react16.12+
  • antd3+
  • react-router5+
  • eslint6+

初始化專案

  1. 建立一個目錄,名字按自己喜好來
    mkdir react-ts-template
    cd react-ts-template
    複製程式碼
  2. 初始化專案,填寫專案資訊
    yarn init -y 或者 npm init -y
    複製程式碼

安裝webpack

yarn add webpack -D 或者 npm i webpack -D
yarn add webpack-cli -D 或者 npm i webpack-cli -D
複製程式碼
  • webpack也可以全域性安裝,不過要注意配置PATH
  • webpack4將命令列相關的操作抽離到了webpack-cli中,比如init、migrate、serve等等,不過都沒用過

安裝完畢後在根目錄新建build資料夾,並新建一個webpack.common.js檔案,用來存放webpack的公共配置

mkdir build
cd build
touch webapck.common.js
複製程式碼

然後在webpack.common.js中簡單的配置入口(entry)跟輸出(output)。

const path = require('path');
module.exports={
  entry: path.join(__dirname, '../src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, '../dist')
  }
}
複製程式碼

接著在根目錄下再新建src資料夾,用來存放主要程式碼,並新建index.js,隨便寫點東西。

console.log('Hello World');
複製程式碼

在package.json中加入一個指令碼,並在控制檯中執行它npm run build

"scripts": {
    "build": "webpack --config build/webpack.common.js"
}
複製程式碼

之後會發現生成了一個dist資料夾,並且還有一個bundle.js,同時控制檯還會有報錯

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
複製程式碼

webpack4中提供了 mode 配置選項,告知 webpack 使用相應模式的內建優化,上面這個警告寫著如果不提供mode,webpack將會使用production模式。我們把scripts修改一下。

"scripts": {
    "build": "webpack --config build/webpack.common.js --mode production"
}
複製程式碼

這樣的webpack簡單的打包功能就有了。

Babel

Babel這裡使用的是7版本,與之前版本不同的是安裝依賴時的包名,像babel-core、babel-preset-env、babel-polyfill等,名字已經更換成了@babel/core、@babel/preset-env、@babel/polyfill,這裡先安裝主要的包。

yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader -D
複製程式碼

然後在根目錄下新建一個babel.config.js,這個配置檔案跟.babelrc.js是有區別的,根據官網來看babel.config.js是專案級別的一個配置,詳細資訊可以參照官網 Config Files,在其中新增如下內容:

module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-react'
  ],
  plugins: []
}
複製程式碼

修改webpack.common.js,增加 js 檔案的 loader 配置,之後還會改。

  module: {
    rules: [{
        test: /\.js$/,
        use: ['babel-loader'],
        include: path.join(__dirname, '../src')
    }]
  }
複製程式碼

React

接下來加入React,也是最重要的部分。

yarn add react react-dom
複製程式碼

修改 src/index.js 中的內容

import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(<div>Hello React!</div>, document.getElementById('root'));
複製程式碼

然後在根目錄下新建一個public資料夾,並在裡面新建一個index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React-TS-Tempalte</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
複製程式碼

想要 webpack 能以這個html為模板,還需要一個html-webpack-plugin外掛,安裝它yarn add html-webpack-plugin -D並在webpack.common.js中增加如下配置,這也是用到的第一個外掛:

const HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'public/index.html',
      inject: true
    })
]
複製程式碼

讓我們打包後開啟dist下的index.html看看效果,成功地展示了Hello React。

搭建自己的React+Typescript環境(一)

開發環境配置

接下來安裝 webpack-dev-server 來啟動一個簡單的伺服器。

yarn add webpack-dev-server -D
複製程式碼

修改webpack.common.config.js,增加webpack-dev-server的配置。

  devServer: {
    host: 'localhost',
    port: 3000,
    historyApiFallback: true,
    overlay: {
      //當出現編譯器錯誤或警告時,就在網頁上顯示一層黑色的背景層和錯誤資訊
      errors: true
    },
    inline: true,
    hot: true
  }
複製程式碼

接下來需要在package.json中增加一個script

"scripts": {
    "dev": "webpack-dev-server --config build/webpack.common.js --mode development --open"
}
複製程式碼

在控制檯中輸入npm run dev,便可以在http://localhost:3000 中看到啟動的專案。

Typescript

下面加入Typescript依賴,關鍵的依賴包就是 typescript,不過還需要安裝對應的types:@types/react、@types/react-dom。

yarn add typescript @types/react @types/react-dom -D
複製程式碼

接下來需要把之前的 js、jsx 檔案替換成對應的 ts、tsx,同時還需要對應的loader,可以使用 ts-loader 以及之前安裝過的 babel-loader,這裡使用之前安裝的 babel-loader,在webpack.common.js中新增配置:

rules: [
  {
    test: /\.(j|t)sx?$/,
    include: [resolve('../src')],
    use: [
      {
        loader: 'babel-loader'
      }
    ],
    // 排除node_modules底下的
    exclude: /node_modules/
  }
]
複製程式碼

修改 webpack 的入口配置

module.exports={
  entry: path.join(__dirname, '../src/index.tsx')
}
複製程式碼

不要忘記把src下的index.js改成index.tsx

配置tsconfig.json,這個檔案也是使用ts時很關鍵的一個檔案,下面是官網的推薦配置。

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
    "module": "esnext",                       /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],                                        /* Specify library files to be included in the compilation. */
    "allowJs": true,                          /* Allow javascript files to be compiled. */
    "jsx": "react",                           /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "sourceMap": true,                        /* Generates corresponding '.map' file. */
    "outDir": "./dist",                       /* Redirect output structure to the directory. */
    "isolatedModules": true,                  /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
    "resolveJsonModule": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "strict": true,                           /* Enable all strict type-checking options. */
    "noImplicitThis": true,                   /* Raise error on 'this' expressions with an implied 'any' type. */
    "noImplicitReturns": true,                /* Report error when not all code paths in function return a value. */
    "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": ".",                       /* Base directory to resolve non-absolute module names. */
    "paths": {},                                        /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    "allowSyntheticDefaultImports": true,     /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "experimentalDecorators": true,           /* Enables experimental support for ES7 decorators. */
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ]
}
複製程式碼

CSS配置

這裡我們需要用到 style-loadercss-loader,先安裝它們

  • css-loader使你能夠使用類似@import 和 url(...)的方法實現 require()的功能;
  • style-loader將所有的計算後的樣式加入頁面中; 二者組合在一起使你能夠把樣式表嵌入webpack打包後的JS檔案中。
yarn add style-loader css-loader -D
複製程式碼

然後在webpack.common.js中新增相應的規則

{
    test: /\.css$/, // 正則匹配檔案路徑
    exclude: /node_modules/,
    use: [
      // 注意loader生效是從下往上的
      'style-loader',
      'css-loader'
    ]
 }
複製程式碼

接著在webpack.common.js中配置resolve.extensions,來自動解析確定的擴充套件。

  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx']
  }
複製程式碼

在根目錄下新建一個index.css,以及App.tsx,並在index.tsx中引入它們

// index.css
.app {
    background-color: red;
}

// App.tsx
import * as React from 'react'

class App extends React.Component {
  render() {
    return (
      <div className="app">
        Hello React
      </div>
    )
  }
}
export default App

// index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))

複製程式碼

啟動後便可以看到設定的紅色背景

加入Sass

如果想要在編寫樣式時使用sass的語法,就需要安裝相應的loader。

yarn add sass-loader node-sass -D
複製程式碼

注意:node-sass安裝時可能會遇到網路問題,需要使用淘寶映象源。

安裝完成後在webpack.common.js中加入 .scss 檔案的規則

{
    test: /\.scss$/,
    include: path.join(__dirname, '../src'), // 只讓loader解析我們src底下自己寫的檔案
    use: [
      'style-loader',
      'css-loader',
      'sass-loader'
    ]
}
複製程式碼

接下來我們把根目錄下的index.css改成index.scss,不要忘記index.tsx中引入的檔案字尾也要修改,專案啟動後發現可以成功解析scss檔案。

配置公共sass屬性

既然已經可以使用sass進行更加簡便的css程式碼編寫,那麼我們也可以將常用的一些樣式程式碼和sass變數寫入公共檔案中,當使用的時候就可以直接引入使用。

在src目錄下新建styles資料夾,然後新建一個var.scss檔案用於存放樣式變數。 之後在var.scss檔案裡寫入一個顏色變數和一個樣式:

$red: red;
@mixin ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
複製程式碼

然後在剛才的index.scss檔案中引入它。

@import './styles/var.scss';

.app{  
  background: $red;
  .aps {
    width: 50px;
    @include ellipsis;
  }
}
複製程式碼

這樣配置之後還存在一個優化上的問題,如果需要在不同的層級引入var.scss就要根據每個資料夾的路徑相對來引入非常麻煩,那麼我們能否做到只需要@import var.scss就行呢?答案是可以的,我們可以通過配置loader的屬性includePaths進行路徑優化,修改webpack.common.js。

{
    test: /\.scss$/,
    include: path.join(__dirname, '../src'),
    use: [
      'style-loader',
      'css-loader',
      {
        loader: 'sass-loader',
        include: resolve('../src'),
        options: {
          // 這個在最新的scss版本中已經不能用了2020-01-05更新,額其實一兩個月前就不能這樣寫了
          // includePaths: [path.join(__dirname, '../src/styles')]
          // 應換成下面的
          sassOptions: {
            includePaths: [path.join(__dirname, '../src/styles')]
          }
        }
      }
    ]
}
複製程式碼

這樣之後我們在引入的時候只需要寫檔名稱即可。

@import 'var.scss';
複製程式碼

加入PostCSS

什麼是PostCSS呢?借用官方的話:

PostCSS 是一個允許使用 JS 外掛轉換樣式的工具。 這些外掛可以檢查(lint)你的 CSS,支援 CSS Variables 和 Mixins, 編譯尚未被瀏覽器廣泛支援的先進的 CSS 語法,內聯圖片,以及其它很多優秀的功能。

它提供了很多常用的外掛

  • 提前使用先進的 CSS 特性
    • autoprefixer 新增了 vendor 瀏覽器字首,它使用 Can I Use 上面的資料。
    • postcss-preset-env 允許你使用未來的 CSS 特性。
  • 更佳的 CSS 可讀性
    • precss 囊括了許多外掛來支援類似 Sass 的特性,比如 CSS 變數,套嵌,mixins 等。
  • 圖片和字型
    • postcss-assets 可以插入圖片尺寸和內聯檔案。
    • postcss-sprites 能生成雪碧圖。

...還有很多,具體可以檢視PostCSS中文Readme

這裡主要用autoprefixer,首先安裝postcss-loader

yarn add postcss-loader autoprefixer -D
複製程式碼

之後在根目錄下新建一個postcss.config.js檔案,並寫入:

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}
複製程式碼

最後需要在webpack.common.js的樣式相關外掛的 css-loader 之後加上配置,以scss為例

{
    test: /\.scss$/,
    include: path.join(__dirname, '../src'),
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader', // 加了這一行
      {
        loader: 'sass-loader',
        options: {
          includePaths: [path.join(__dirname, '../src/styles')]
        }
      }
    ]
 }
複製程式碼

隨便寫點樣式,然後在谷歌控制檯可以發現,會自動幫你新增 -webkit- 的字首。

注意如果使用了 postcss-preset-env 這個的話,它會自動安裝 autoprefixer,並且配置了它,就不再需要配置 autoprefixer

const postcssPresetEnv = require('postcss-preset-env');
module.exports = {
  plugins: [
    postcssPresetEnv(/* pluginOptions */)
  ]
}
複製程式碼

CSS Modules優化

CSS Modules 是為了加入區域性作用域和模組依賴,這裡我沒加入它,可以代替它的方案也有,比如scoped,以及bem命名方式等,這裡我選擇了bem命名方法,掘金也有關於它的介紹,可以去看看。

圖片字型等資源載入

首先安裝處理這類資源的載入器

yarn add url-loader file-loader -D
複製程式碼

然後在webpack.common.js中加入相關的規則配置

  {
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    use: [
      {
      loader: 'url-loader',
      options: {
        //1024 == 1kb  
        //小於10kb時打包成base64編碼的圖片否則單獨打包成圖片
        limit: 10240,
        name: path.join('img/[name].[hash:7].[ext]')
      }
    }]
  },
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    use: [{
      loader: 'url-loader',
      options: {
        limit: 10240,
        name: path.join('font/[name].[hash:7].[ext]')
      }
    }]
  }
複製程式碼

ESlint

關於typescript程式碼的規範校驗,可選的有tslint和eslint,不過最近官方也推薦往eslint上轉了,所以我這裡使用eslint,安裝相關依賴。

yarn add eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser
複製程式碼

如果不使用 hook 的話,就不用裝 eslint-plugin-react-hooks 這個外掛了

在根目錄下新建.eslintrc.js,並寫入配置:

module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:react/recommended"
  ],
  parserOptions: {
    "ecmaVersion": 2019,
    "sourceType": "module"
  },
  env: {
    node: true,
    browser: true,
    commonjs: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  plugins: [
    "@typescript-eslint",
    "react-hooks"
  ],
  globals: {
    // 這裡填入你的專案需要的全域性變數
    // 這裡值為 false 表示這個全域性變數不允許被重新賦值,比如:
    // React: false,
    // ReactDOM: false
  },
  settings: {
    react: {
        pragma: "React",
        version: "detect"
    }
  },
  rules: {
    // 這裡填入你的專案需要的個性化配置,比如:
    //
    // // @fixable 一個縮排必須用兩個空格替代
    semi: ['error', 'never'],
    'no-console': 'off',
    'no-unused-vars': [
      'warn',
      {
        vars: 'all',
        args: 'none',
        caughtErrors: 'none'
      }
    ],
    'max-nested-callbacks': 'off',
    'react/no-children-prop': 'off',
    'typescript/member-ordering': 'off',
    'typescript/member-delimiter-style': 'off',
    'react/jsx-indent-props': 'off',
    'react/no-did-update-set-state': 'off',
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    indent: [
      'off',
      2,
      {
        SwitchCase: 1,
        flatTernaryExpressions: true
      }
    ]
  }
}
複製程式碼

用 VS Code 開發時,應該還需要配置settings.json

    "eslint.autoFixOnSave": true,
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
            "language": "html",
            "autoFix": true
        },
        {
            "language": "vue",
            "autoFix": true
        },
        {
            "language": "typescript",
            "autoFix": true
        },
        {
            "language": "typescriptreact",
            "autoFix": true
        },
    ]
複製程式碼

AntDesign

antd是阿里家的一款UI元件庫,官方文件中關於如何引入使用講的很清楚,我們來配置一下,先安裝需要的依賴,babel-plugin-import 用於按需引入:

yarn add antd
yarn add babel-plugin-import less less-loader -D 
複製程式碼

在babel.config.js中增加配置

  plugins: [
    ...
    ['import', { 
      libraryName: 'antd',
      libraryDirectory: 'lib',
      style: true 
    }]
  ]
複製程式碼

還需要在webpack.common.js中配置less規則

  {
    // for ant design
    test: /\.less$/,
    include: resolve('../node_modules'),
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader',
      {
        loader: 'less-loader',
        options: {
          javascriptEnabled: true,
          modifyVars: theme
        }
      }
    ]
   }
複製程式碼

注意 modifyVars: theme 這個是根據官網來的 自定義主題,需要新建一個 theme.js,這個檔案的名字自己定義,這裡修改了一下主要顏色,以及 border-radius-base 的值,還有很多配置具體可以檢視官網。

module.exports = {
  'primary-color': 'black',
  'border-radius-base': '10px'
}
複製程式碼

之後便可以按照官網例項愉快的使用了。不過打包之後存在一個問題,icons.js佔了很大的一部分,這裡使用github上的大佬給出的解決方案。

在src下新建一個icons.ts,裡面只寫用到的icon

export { default as DownOutline } from '@ant-design/icons/lib/outline/DownOutline'
複製程式碼

然後配置別名,這裡就將在ts中使用別名的方式一塊介紹了。在 webpack.common.js 中的 resolve 項中增加下面配置,跟icon有關的是第二行。

alias: {
  '@': resolve('../src'),
  "@ant-design/icons/lib/dist$": resolve('../src/icons.ts'),
  '@components': resolve('../src/components'),
  '@img': resolve('../src/assets/img')
}
複製程式碼

還需要在tsconfig.json中增加配置:

"paths": {
  "@/*": ["src/*"],
  "@ant-design/icons/lib/dist$": ["src/icons.js"],
  "@components/*": ["src/components/*"],
  "@img/*": ["src/assets/img/*"]
}
複製程式碼

更新babel配置

之前加了 typescript 等依賴,現在來更新一下 babel 的配置,來支援我們後面可能會用到的功能,比如裝飾器以及路由的動態引入。

yarn add @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-syntax-dynamic-import -D
複製程式碼

修改 babel.config.js

module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-typescript',
    '@babel/preset-react'
  ],
  plugins: [
    ['import', { 
      libraryName: 'antd',
      libraryDirectory: 'lib',
      style: true 
    }],
    ['@babel/plugin-proposal-decorators', { legacy: true }],
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    '@babel/plugin-syntax-dynamic-import'
  ]
}
複製程式碼

React-router

router使用最新的5版本,然後路由懶載入使用官方例子中的loadable,首先還是安裝依賴

yarn add react-router-dom
yarn add @loadable/component @types/loadable__component @types/react-router-dom -D
複製程式碼

讓我們在 App.tsx 中使用它們

import * as React from 'react'
import { HashRouter as Router, Route, Link } from "react-router-dom"
import loadable from '@loadable/component'

const HomeComponent = loadable(() => import(/* webpackChunkName: "home" */ './views/Home'))
const AboutComponent = loadable(() => import(/* webpackChunkName: "about" */ './views/About'))

class App extends React.Component {
  
  
  render() {

    return (
      <div className="app">
        <Router>
          <ul>
            <li>
              <Link to="/">To Home</Link>
            </li>
            <li>
              <Link to="/about">To About</Link>
            </li>
          </ul>
          <Route exact path='/' component={HomeComponent}></Route>
          <Route path='/about' component={AboutComponent}></Route>
        </Router>
        <p className="aps">hahahaahhahhahahaha</p>
      </div>
    )
  }
}

export default App
複製程式碼

更多

到這裡基本的功能已經具備了,接下來還需要一些優化以及拆分開發和生產環境的配置,篇幅有點長了,放到下一篇文章裡寫吧。

最後附上地址 專案地址,如果有不對的地方希望各位指出,感謝。

相關文章