從零開始搭建一個 React + Mobx + React Router 腳手架

暖生發表於2019-04-11

本文詳細介紹瞭如何從零開始搭建一個 React 開發的腳手架,包含如何新增 Redux 以及 React Router 的環境。

本文程式碼地址:react-mobx-starter

建議將程式碼拉下來之後,配合本文一起檢視,效果更佳。

程式碼下載命令:

git clone https://github.com/beichensky/react-mobx-starter.git
複製程式碼

最近將腳手架中的 babel 配置更新到了 7.0.0 版本,所以部分地方作出了修改。目前腳手架中的各類庫的版本如下:

  • node:10.15.3
  • npm:6.4.1
  • webpack: 4.29.6
  • webpack-cli: 3.3.0
  • webpack-dev-server: 3.1.4
  • @babel/core: 7.0.0
  • react: 16.8.6
  • mobx: 5.9.4
  • react-router-dom: 5.0.0

如果是從低版本升級到 7.0.0 版本,官方提供了一個新的命令可以直接幫助我們對專案進行更新:

npx babel-upgrade --write --install
複製程式碼

更多關於 babel-upgrade 的介紹可以參考官方說明


一、前情提要

本文的 Demo 分為兩個環境,一個是開發環境,另一個是生產環境。

  • 開發環境中講述的是如何配置出一個更好的、更方便的開發環境;

  • 而生產環境中講述的是如何配置出一個更優化、更小版本的生產環境。

之前我也就 Webpack 的使用寫了幾篇文章,本文也是在 Webpack 的基礎上進行開發,也是在之前的程式碼上進行的擴充套件。

建議:對於 Webpack 還不瞭解的朋友,可以先看一下 從零開始搭建一個 Webpack 開發環境配置(附 Demo)使用 Webpack 進行生產環境配置(附 Demo) 這兩篇文章,可以更好的入手本文。

雖然本文是在之前文章上進行的擴充套件,但本文還是會詳細的介紹每一步的配置。


二、建立專案結構

新建資料夾,命名為:react-mobx-starter

mkdir react-mobx-starter 
複製程式碼

初始化 package.json 檔案

cd react-mobx-starter

# 直接生成預設的 package.json 檔案
npm init -y
複製程式碼

建立 src 目錄,用來存放我們編寫的程式碼 建立 public 目錄,用來存放公共的檔案 建立 webpack 目錄,用來存放 webpack 配置檔案

mkdir src

mkdir public

mkdir webpack
複製程式碼

在 src 目錄下 新建 pages 資料夾,用來存放書寫的頁面元件 新建 components 資料夾,用來存放公共元件 新建 utils 資料夾,用來存放常用的工具類

cd src

mkdir pages

mkdir components

mkdir utils
複製程式碼

public 目錄下新建 index.html 檔案 在 src 目錄下新建 index.js 檔案 在 webpack 目錄下建立 webpack.config.dev.jswebpack.config.prod.js

  • webpack.config.dev.js 用來編寫 webpack 開發環境配置
  • webpack.config.prod.js 用來編寫 webpack 生產環境配置

index.html

<!DOCTYPE html>
<html lang="en">
<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 + Mobx 全家桶腳手架</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
複製程式碼

index.js

function createElement() {
    const ele = document.createElement('div');
    ele.innerHTML = 'Hello, React';
    const root = document.querySelector('#root');
    root.appendChild(ele);
}

createElement();
複製程式碼

webpack.config.dev.jswebpack.config.prod.js 此時還沒有書寫內容,我們之後會詳細的進行講述。

我們看一下此時的專案結構,之後就可以進行 webpack 的配置了。

react-mobx-starter
  ├─ public/
    └─ index.html
  ├─ src/
    ├─ components/
    ├─ pages/
    ├─ utils/
    └─ index.js
  ├─ webpack/
    ├─ webpack.config.dev.js
    └─webpack.config.prod.js
  ├─ package.json
複製程式碼

三、React 開發環境配置

在 package.json 檔案中新增一個執行指令碼,用來執行 webpack 命令:

{
    ...,
    "scripts": {
        "start": "webpack --config webpack/webpack.config.dev.js"
    },
    ...
}
複製程式碼

安裝 webpack 相關外掛

安裝 webpack 和 webpack-cli

npm install webpack webpack-cli --save-dev
複製程式碼

入口和出口

使用 webpack 進行專案配置的時候,必須要有入口和出口,作為模組引入和專案輸出。

webpack.config.dev.js

const path = require('path');

const appSrc = path.resolve(__dirname, '../src');
const appDist = path.resolve(__dirname, '../dist');
const appPublic = path.resolve(__dirname, '../public');
const appIndex = path.resolve(appSrc, 'index.js');

module.exports = {
    entry: appIndex,
    output: {
        filename: 'public/js/[name].[hash:8].js',
        path: dist,
        publicPath: '/'
    }
}
複製程式碼

新增 html-webpack-plugin 外掛

執行 npm run start 指令碼,可以看到 dist/public/js 目錄下多了一個 js 檔案,但是這個是由 hash 值命名的的,我們每次都手動引入到 index.html 檔案裡面過於麻煩,所以可以引入 html-webpack-plugin 外掛。

html-webpack-plugin 外掛有兩個作用

  • 可以將 public 目錄下的資料夾拷貝到 dist 輸出資料夾下
  • 可以自動將 dist 下的 js 檔案引入到 html 檔案中

安裝 html-webpack-plugin 外掛

npm install html-webpack-plugin --save-dev
複製程式碼

使用 html-webpack-plugin 外掛

webpack.config.dev.js


const path = require('path');
+ const HTMLWebpackPlugin = require('html-webpack-plugin');

const appSrc = path.resolve(__dirname, '../src');
const appDist = path.resolve(__dirname, '../dist');
const appPublic = path.resolve(__dirname, '../public');
const appIndex = path.resolve(appSrc, 'index.js');
+ const appHtml = path.resolve(appPublic, 'index.html');

module.exports = {
    entry: appIndex,
    output: {
        filename: 'public/js/[name].[hash:8].js',
        path: appDist,
        publicPath: '/'
    },
+    plugins: [
+        new HTMLWebpackPlugin({
+            template: appHtml,
+            filename: 'index.html'
+        })
+    ]
}
複製程式碼

設定開發模式

webpack 配置中的 mode 屬性,可以設定為 'development' 和 'production',我們目前是進行開發環境配置,所以可以設定為 'development'

webpack.config.dev.js

...
module.exports = {
+    mode: 'development',
    ...
}
複製程式碼

設定 devtool

為了方便在專案出錯時,迅速定位到錯誤位置,可以設定 devtool,生成資源對映,我們這裡使用 inline-source-map,更多選擇可以在這裡檢視區別。

webpack.config.dev.js

...
module.exports = {
    mode: 'development',
+    devtool: 'inline-source-map',
    ...
}
複製程式碼

使用 webpack-dev-server 啟動專案服務

安裝 webpack-dev-server

npm install webpack-dev-server --save-dev
複製程式碼

配置 webpack-dev-server

webpack.config.dev.js

...
module.exports = {
    mode: 'development',
    devtool: 'inline-source-map',
+    devServer: {
+        contentBase: appPublic,
+        hot: true,
+        host: 'localhost',
+        port: 8000,
+        historyApiFallback: true,
+        // 是否將錯誤展示在瀏覽器蒙層
+        overlay: true,
+        inline: true,
+        // 列印資訊
+        stats: 'errors-only',
+        // 設定代理
+        proxy: {
+            '/api': {
+                changeOrigin: true,
+                target: 'https://easy-mock.com/mock/5c2dc9665cfaa5209116fa40/example',
+                pathRewrite: {
+                    '^/api/': '/'
+                }
+            }
+        }
+    },
    ...
}
複製程式碼

修改一下 package.json 檔案中的 start 指令碼:

{
    ...,
    "scripts": {
        "start": "webpack-dev-server --config webpack/webpack.config.dev.js"
    },
    ...
}
複製程式碼

使用 friendly-errors-webpack-plugin 外掛

friendly-errors-webpack-plugin 外掛可以在命令列展示更有好的提示功能。

安裝 friendly-errors-webpack-plugin

npm install friendly-errors-webpack-plugin --save-dev
複製程式碼

使用 friendly-errors-webpack-plugin

webpack.config.dev.js


const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
+ const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

...

module.exports = {
    ...
    plugins: [
        new HTMLWebpackPlugin({
            template: appHtml,
            filename: 'index.html'
        }),
+        new FriendlyErrorsWebpackPlugin(),
    ]
}
複製程式碼

啟用熱載入

webpack.config.dev.js

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
+ const webpack = require('webpack');

...

module.exports = {
    ...
    plugins: [
        ...
        new FriendlyErrorsWebpackPlugin(),
+        new webpack.HotModuleReplacementPlugin()
    ]
}
複製程式碼

執行專案,測試配置成果

執行 npm run start 命令,命令列提示成功後,在瀏覽器開啟 http://localhost:8000,可以看到 Hello React,說明基本的Webpack 配置已經成功了。

配置 babel

我們現在 index.js 裡面的程式碼量比較少,所以沒有問題。但是我如果想在裡面使用一些 ES6 的語法或者是還未被標準定義的 JS 特性,那麼我們就需要使用 babel 來進行轉換了。下面我們來配置一下 babel

安裝 babel 相關外掛

npm install @babel/core babel-loader --save-dev
複製程式碼

使用 babel-loader

設定 cacheDirectory 屬性,指定的目錄將用來快取 loader 的執行結果。之後的 webpack 構建,將會嘗試讀取快取,來避免在每次執行時,可能產生的、高效能消耗的 Babel 重新編譯過程。

webpack.config.dev.js

...
module.exports = {
    ...
    plugins: [ ... ],
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                loader: 'babel-loader?cacheDirectory',
                include: [ appSrc ],
                exclude: /node_modules/
            }
        ]
    }
}
複製程式碼

在專案根目錄下新建 babel.config.js 檔案

babel.config.js 檔案是執行時控制檔案,在專案編譯的時候會自動讀取 babel.config.js 檔案中的 babel 配置。

使用 babel 相關 presets

安裝相關外掛:

  • @babel/preset-env:可以在專案中使用所有 ECMAScript 標準裡的最新特性。
  • @babel/preset-react:可以在專案中使用 react 語法。
npm install babel-preset-env babel-preset-react --save-dev
複製程式碼

配置 babel.config.js 檔案:

module.exports = (api) => {
    api.cache(true);

    return {
        presets: [
            "@babel/preset-env",
            "@babel/preset-react"
        ]
    }
}
複製程式碼

使用 babel 相關 plugins

babel 升級到 7.0.0 版本之後, @babel/preset-stage-0 被廢棄,用到的外掛需要自己進行安裝。 如果是從低版本升級到 7.0.0 版本,官方提供了一個新的命令可以直接幫助我們對專案進行更新:

npx babel-upgrade --write --install
複製程式碼

更多關於 babel-upgrade 的介紹可以參考官方說明

安裝相關外掛:

  • @babel/plugin-proposal-decorators:可以在專案中使用裝飾器語法。
  • @babel/plugin-proposal-class-properties:可以在專案中使用新的 class 屬性語法。
  • @babel/plugin-transform-runtime:使用此外掛可以直接使用 babel-runtime 中的程式碼對 js 檔案進行轉換,避免程式碼冗餘。
  • @babel/runtime-corejs2:配合 babel-plugin-transform-runtime 外掛成對使用
  • @babel/plugin-syntax-dynamic-import:可以在專案中使用 import() 這種語法
  • @babel/plugin-proposal-export-namespace-from:可以使用 export * 這種名稱空間的方式匯出模組
  • @babel/plugin-proposal-throw-expressions:可以使用異常丟擲表示式
  • @babel/plugin-proposal-logical-assignment-operators:可以使用邏輯賦值運算子
  • @babel/plugin-proposal-optional-chaining:可以使用可選鏈的方式訪問深層巢狀的屬性或者函式 ?.
  • @babel/plugin-proposal-pipeline-operator:可以使用管道運算子 |>
  • @babel/plugin-proposal-nullish-coalescing-operator:可以使用空值合併語法 ??
  • @babel/plugin-proposal-do-expressions:可以使用 do 表示式(可以認為是三元運算子的複雜版本)
  • @babel/plugin-proposal-function-bind:可以使用功能繫結語法 obj::func
npm install @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime @babel/runtime-corejs2 @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-export-namespace-from @babel/plugin-proposal-throw-expressions @babel/plugin-proposal-logical-assignment-operators @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-pipeline-operator @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-do-expressions @babel/plugin-proposal-function-bind --save-dev
複製程式碼

配置 babel.config.js 檔案:

module.exports = (api) => {
    api.cache(true);

    return {
        presets: [
            "@babel/preset-env",
            "@babel/preset-react"
        ],
        plugins: [
            [
                "@babel/plugin-proposal-decorators",
                {
                    "legacy": true
                }
            ],
            [
                "@babel/plugin-transform-runtime",
                {
                    "corejs": 2
                }
            ],
            [
                "@babel/plugin-proposal-class-properties", 
                { 
                    "loose": true
                }
            ],
            "@babel/plugin-syntax-dynamic-import",
            // 可以使用 export * 這種名稱空間的方式匯出模組
            "@babel/plugin-proposal-export-namespace-from",
            // 可以使用異常丟擲表示式,
            "@babel/plugin-proposal-throw-expressions",
            // 預設匯出
            "@babel/plugin-proposal-export-default-from",
            // 可以使用邏輯賦值運算子
            "@babel/plugin-proposal-logical-assignment-operators",
            // 可以使用可選鏈的方式訪問深層巢狀的屬性或者函式 ?.
            "@babel/plugin-proposal-optional-chaining",
            // 可以使用管道運算子 |> 
            [
                "@babel/plugin-proposal-pipeline-operator",
                {
                    "proposal": "minimal"
                }
            ],
            // 可以使用空值合併語法 ??
            "@babel/plugin-proposal-nullish-coalescing-operator",
            // 可以使用 do 表示式(可以認為是三元運算子的複雜版本)
            "@babel/plugin-proposal-do-expressions",
            // 可以使用功能繫結語法 obj::func
            "@babel/plugin-proposal-function-bind"
        ]
    }
}

複製程式碼

這裡需要注意 @babel/plugin-proposal-decorators 外掛的放置順序,最好放在第一位,否則可能會出現某些註解失效的問題。

至此,babel 相關的基本配置完成了。之後我們就可以在專案中肆意使用各種新的 JS 特性了。

新增 css 相關 loader

js 檔案相關的 babel-loader 配置好了,但是有時候我們想在專案中為元素新增一些樣式,而 webpack 中認為一切都是模組,所以我們這時候也需要別的 loader 來解析一波樣式程式碼了。

安裝相關外掛:

  • css-loader:處理 css 檔案中的 url() 等。
  • style-loader:將 css 插入到頁面的 style 標籤。
  • less-loader:是將 less 檔案編譯成 css
  • postcss-loader:可以整合很多外掛,用來操作 css。我們這裡使用它整合 autoprefixer 來自動新增字首。
npm install css-loader style-loader less less-loader postcss-loader autoprefixer --save-dev
複製程式碼

配置樣式相關 loader

  • 由於 React 無法直接使用類似 Vuescope 這種區域性作用變數,所以我們可以使用 webpack 提供的 CSS Module。 2、由於等會兒會使用 antd,所以引入 antd 時需要開啟 lessjavascript 選項,所以要將 less-loader 中的屬性 javascriptEnabled 設定為 true

在 webpack.config.dev.js 中配置:

...
const autoprefixer = require('autoprefixer');

module.exports = {
    ...,
    plugins: [...],
    module: {
        rules: [
            ...,
            {
                test: /\.(css|less)$/,
                exclude: /node_modules/,
                use: [{
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                            modules: true,
                            localIdentName: '[local].[hash:8]'
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: () => [autoprefixer()]
                        }
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
            {
                test: /\.(css|less)$/,
                include: /node_modules/,
                use: [{
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader',
                        options: {}
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: () => [autoprefixer()]
                        }
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
        ]
    }
}
複製程式碼

新增其他模組解析 loader 配置

安裝相關外掛:

npm install file-loader csv-loader xml-loader html-loader markdown-loader --save-dev
複製程式碼

在 webpack.config.dev.js 中配置:

...

module.exports = {
    ...,
    plugins: [...],
    module: {
        rules: [
            ...,
            // 解析圖片資源
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析 字型
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析資料資源
            {
                test: /\.(csv|tsv)$/,
                use: [
                    'csv-loader'
                ]
            },
            // 解析資料資源
            {
                test: /\.xml$/,
                use: [
                    'xml-loader'
                ]
            },
            // 解析 MakeDown 檔案
            {
                test: /\.md$/,
                use: [
                    'html-loader',
                    'markdown-loader'
                ]
            }
        ]
    }
}
複製程式碼

額外的 webpack 配置優化

新增 resolve allias 屬性,設定別名

在專案開發過程中,隨著專案越來越大, 檔案層級越來越深,引入檔案的時候可能會需要一層一層的找路徑,就會比較繁瑣,我們可以使用 resolve 中的 alias 屬性為一些常用的資料夾設定別名

webpack.config.dev.js

··· 

module.exports = {
    ...,
    plugins: [...],
    module: {...},
+    resolve: {
+        alias: {
+            src: appSrc,
+            utils: path.resolve(__dirname, '../src/utils'),
+            pages: path.resolve(__dirname, '../src/pages'),
+            components: path.resolve(__dirname, '../src/components')
+        }
+    }
}
複製程式碼

新增 resolve.modules 屬性,指明第三方模組存放位置

我們知道,一般進行模組搜尋時,會從當前目錄下的 node_modules 一直搜尋到磁碟根目錄下的 node_modules。所以為了減少搜尋步驟,我們可以設定 resolve.modules 屬性強制只從專案的 node_modules 中查詢模組。

webpack.config.dev.js

··· 

module.exports = {
    ...,
    plugins: [...],
    module: {...},
    resolve: {
        ...,
+        modules: [path.resolve(__dirname, '../node_modules')],
    }
}
複製程式碼

安裝 React、MObx 以及 React Router 相關外掛

npm install react react-dom prop-types mobx mobx-react react-router-dom --save
複製程式碼

引入 antd

按照 antd 官網的說明,直接在 babel.config.js 檔案中新增配置,之後即可在專案中正常使用了。

安裝 antd 相關外掛:

npm install antd moment --save
複製程式碼

安裝 babel-plugin-import 對元件進行按需載入:

npm install babel-plugin-import --save-dev
複製程式碼

babel.config.js 檔案中新增 antd 配置:

module.exports = (api) => {
    api.cache(true);
    
    return {
        presets: [
            ...
        ],
        plugins: [
            ...,
    +        [
    +            "import",
    +            {
    +                "libraryName": "antd",
    +                "style": true
    +            }
    +        ],
            ...
        ]
    }
複製程式碼

四、進行 React 開發

基本上需要的外掛目前都已經引入了,是時候進行開發了。

修改根目錄下的 index.js 檔案

index.js

import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'mobx-react'
import { LocaleProvider } from 'antd';
import { HashRouter } from 'react-router-dom';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import 'moment/locale/zh-cn';

import GlobalModel from './GlobalModel';
// import App from './App';

const globalModel = new GlobalModel();

const App = () => {
    return <div>開發環境配置完成</div>
}

ReactDom.render(
    <Provider globalModel={ globalModel }>
        <LocaleProvider locale={zh_CN}>
            <HashRouter>
                <App />
            </HashRouter>
        </LocaleProvider>
    </Provider>,
    document.querySelector('#root')
);
複製程式碼

執行 npm run start 命令,在瀏覽器開啟 http://localhost:8000/,就能夠看到 開發環境配置完成 正常顯示。

此時說明我們各種外掛、庫都已經引入完成,可以正常使用了。

使用 React Route 進行頁面間路由跳轉

在 src 目錄下新建 App.js 檔案:

App.js

import React from 'react';
import { Switch, Route } from 'react-router-dom';

import Home from 'pages/home';
import Settings from 'pages/settings';
import Display from 'pages/display';
import NotFound from 'pages/exception'

import styles from './App.less';


export default (props) => {
    return (
        <div className={ styles.app }>
            <Switch>
                <Route path='/settings' component={ Settings } />
                <Route path='/display' component={ Display } />
                <Route exact path='/' component={ Home } />
                <Route component={ NotFound } />
            </Switch>
        </div>
    )
}
複製程式碼

src 目錄下建立 App.less 檔案,編寫 App 元件樣式

App.less

.app {
    padding: 60px;
}
複製程式碼

在 pages 目錄下編寫 Home、Settings、Display、NotFound 元件

  • Home 元件是根路由元件,用來跳轉到 Setting 介面和 Display 介面
  • Settings 元件演示瞭如何獲取和修改 mobx 的全域性 Model
  • Display 元件演示瞭如何使用 mobx 進行同步和非同步的資料處理
  • NotFound 元件在匹配不到正確路由時展示

Home、Settings、Display 相關的程式碼我就不貼了,佔的篇幅較長,大家需要的話可以去我的 Github 上看一下或者下載下來也可以。比較方便。地址:Github

再修改一下 index.js 檔案

index.js

import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'mobx-react'
import { LocaleProvider } from 'antd';
import { HashRouter } from 'react-router-dom';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import 'moment/locale/zh-cn';

import GlobalModel from './GlobalModel';
import App from './App';

const globalModel = new GlobalModel();

ReactDom.render(
    <Provider globalModel={ globalModel }>
        <LocaleProvider locale={zh_CN}>
            <HashRouter>
                <App />
            </HashRouter>
        </LocaleProvider>
    </Provider>,
    document.querySelector('#root')
);
複製程式碼

可以看到這裡有一個 GlobalModel 存放全域性通用資料的 Model,裡面的邏輯比較簡單,我們稍微看一下。

GlobalModel.js

import { observable, action } from 'mobx';

export default class GlobalModel {
    @observable username = '小明';

    @action
    changeUserName = (name) => {
        this.username = name;
    }

}
複製程式碼

新增 fetch 工具類進行網路請求

由於我們在 Display 元件中需要進行網路請求的非同步操作,所以我們這裡引入 fetch 進行網路請求。

安裝 fetch 相關外掛:

npm install whatwg-fetch qs --save
複製程式碼

編寫網路請求工具類

utils 目錄下建立 request.js 檔案。

utils/request.js

import 'whatwg-fetch';
import { stringify } from 'qs';

/**
 * 使用 Get 方式進行網路請求
 * @param {*} url 
 * @param {*} data 
 */
export const get = (url, data) => {
    const newUrl = url + '?' + stringify(data) + (stringify(data) === '' ? '' : '&') +'_random=' + Date.now();
    return fetch(newUrl, {
            cache: 'no-cache',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json; charset=utf-8'
            },
            method: 'GET',
        })
        .then(response => response.json());
}

/**
 * 進行 Post 方式進行網路請求
 * @param {*} url 
 * @param {*} data 
 */
export const post = (url, data) => {
    return fetch(url, {
        body: JSON.stringify(data), 
        cache: 'no-cache',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json; charset=utf-8'
        },
        method: 'POST',
    })
    .then(response => response.json()) // parses response to JSON
}
複製程式碼

至此,簡單的 React 框架搭建完成了

執行 npm run start,編譯成功後,可以看到介面長這樣。

Home 介面:

Home 介面

Settings 介面:

Settings 介面

Display 介面:

Display 介面

NotFound 介面:

NotFound 介面


五、進行 React 專案打包配置

先在 package.json 檔案中新增一個執行指令碼:

{
    ...,
    "scripts": {
        "start": "webpack-dev-server --config webpack/webpack.config.dev.js",
        "build": "webpack --config webpack/webpack.config.prod.js"
    },
    ...
}
複製程式碼

配置 webpack.config.prod.js 檔案

其中大部分的 moduleplugin 還有 resolve 都與開發環境的一致。所以我們就以 webpack.config.dev.js 檔案中的配置為基礎進行說明。

  • 將 mode 屬性值修改為:'production'
  • 將 devtool 屬性值修改為:'hidden-source-map'
  • 刪除 devServer 屬性所有的配置。
  • 刪除使用的熱載入外掛:·webpack.HotModuleReplacementPlugin`,

新增 optimization 屬性進行程式碼壓縮

安裝相關外掛:

npm install uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin --save-dev
複製程式碼

新增程式碼壓縮配置:

webpack.config.prod.js

...;
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
...;

module.exports = {
    mode: 'production',
    devtool: 'hidden-source-map',
    entry: ...,
    output: {...},
    plugins: [...],
    module: {...},
    optimization: {
        // 打包壓縮js/css檔案
        minimizer: [
            new UglifyJsPlugin({
                uglifyOptions: {
                    compress: {
                        // 在UglifyJs刪除沒有用到的程式碼時不輸出警告
                        warnings: false,
                        // 刪除所有的 `console` 語句,可以相容ie瀏覽器
                        drop_console: true,
                        // 內嵌定義了但是隻用到一次的變數
                        collapse_vars: true,
                        // 提取出出現多次但是沒有定義成變數去引用的靜態值
                        reduce_vars: true,
                    },
                    output: {
                        // 最緊湊的輸出
                        beautify: false,
                        // 刪除所有的註釋
                        comments: false,
                    }
                }
            }),
            new OptimizeCSSAssetsPlugin({})
        ],
        splitChunks: {
            cacheGroups: {
                styles: {
                    name: 'styles',
                    test: /\.(css|less)/,
                    chunks: 'all',
                    enforce: true,
                    reuseExistingChunk: true // 表示是否使用已有的 chunk,如果為 true 則表示如果當前的 chunk 包含的模組已經被抽取出去了,那麼將不會重新生成新的。
                },
                commons: {
                    name: 'commons',
                    chunks: 'initial',
                    minChunks: 2,
                    reuseExistingChunk: true
                },
                vendors: {
                    name: 'vendors',
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    reuseExistingChunk: true
                }
            }
        },
        runtimeChunk: true
    },
    resolve: {...}
}
複製程式碼

使用 mini-css-extract-plugin 外掛提取 CSS 程式碼

安裝相關外掛:

npm install mini-css-extract-plugin --save-dev
複製程式碼

配置 mini-css-extract-plugin 外掛:

  • plugins 屬性中引入
  • modulerules 中使用的 style-loader 替換為 MiniCssExtractPlugin.loader

webpack.config.prod.js

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

module.exports = {
    ...,
    plugins: [
        ...,
        new MiniCssExtractPlugin({
            filename: 'public/styles/[name].[contenthash:8].css',
            chunkFilename: 'public/styles/[name].[contenthash:8].chunk.css'
        })
    ],
    modules: {
        rules: [
            ...,
            {
                test: /\.(css|less)$/,
                exclude: /node_modules/,
                use: [
                    {
-                        loader: 'style-loader'
+                        loader: MiniCssExtractPlugin.loader
                    },
                    ...
                ]
            },
            {
                test: /\.(css|less)$/,
                exclude: /node_modules/,
                use: [
                    {
-                        loader: 'style-loader'
+                        loader: MiniCssExtractPlugin.loader
                    },
                    ...
                ]
            },
            ...
        ]
    },
    ...
}
複製程式碼

使用 DefinePlugin 外掛定義當前為生產環境

webpack.config.prod.js

...
module.exports = {
    ...,

    plugins: [
        ...,
        new webpack.DefinePlugin({
            // 定義 NODE_ENV 環境變數為 production
            'process.env': {
                NODE_ENV: JSON.stringify('production')
            }
        })
    ],
    ...
}
複製程式碼

使用 clean-webpack-plugin 清理 dist 目錄

打包的過程中,由於部分檔名使用的是 hash 值,會導致每次檔案不同,因而在 dist 中生成一些多餘的檔案。所以我們可以在每次打包之前清理一下 dist 目錄。

安裝外掛:

npm install clean-webpack-plugin --save-dev
複製程式碼

使用外掛:

webpack.config.prod.js

...,

module.exports = {
    ...,
    plugins: [
        ...,
        new CleanWebpackPlugin()
    ],
    ...
}
複製程式碼

新增其他優化配置

  • 新增 stats 配置過濾打包時出現的一些統計資訊。
  • 新增 performance 配置關閉效能提示

webpack.config.prod.js

module.exports = {
    ...,
    stats: {
        modules: false,
        children: false,
        chunks: false,
        chunkModules: false
    },
    performance: {
        hints: false
    }
}
複製程式碼

進行專案的打包釋出

打包專案

執行 npm run build 指令,控制檯打包完成之後,根目錄下多出了 dist 資料夾。

使用 Nginx 釋出專案

我這裡是用的是 Nginx 作為伺服器,釋出在本地。

Nginx 下載地址:nginx.org/en/download…

下載完成之後,解壓完成。開啟 Nginx 目錄,可以找到一個 conf 資料夾,找到其中的 nginx.conf 檔案,修改器中的配置:

Nginx 配置

將圖中標註的 html 更換為 dist

然後我們就可以放心的將打包生成的 dist 資料夾直接放到 Nginx 的安裝目錄下了。(此時 dist 目錄與剛才的 conf 目錄應該是同級的)。

啟動 Nginx 服務:

start nginx
複製程式碼

開啟瀏覽器,輸入 http://127.0.0.1 或者 http://localhost 即可看到我們的專案已經正常的跑起來了。

Nginx 其他命令:

# 停止 Nginx 服務
nginx -s stop

# 重啟 Nginx 服務
nginx -s reload

# 退出 nginx
nginx -s quit
複製程式碼

更多 Ngnix 相關請參考:Nginx官方文件

注意:需要在 Nginx 安裝目下執行 nginx 相關命令!


六、使用 webpack-merge 引入webpack 公共配置

觀察 webpack.config.dev.jswebpack.config.prod.js 檔案,可以發現有大量的程式碼和配置出現了重複。所以我們可以編寫一個 webpack.common.js 檔案,將共有的配置放入其中,然後使用 webpack-merge 外掛分別引入到 webpack.config.dev.jswebpack.config.prod.js 檔案中使用。

外掛安裝:

npm install webpack-merge --save-dev
複製程式碼

使用:

+ const merge = require('webpack-merge');
+ const common = require('./webpack.common.js');

+ module.exports = merge(common, {
+   mode: 'production',
+   ...
+ });
複製程式碼

這裡就展示了一下用法,由於篇幅太長,三個檔案中具體的配置程式碼我就不貼了, 大家可以到 我的 GitHub 上檢視一下使用 webpack-merge 後的配置檔案。


七、本文原始碼地址

react-mobx-starter

歡迎Star,謝謝各位!

文章及程式碼中如有問題,歡迎指正,謝謝!


八、參考文件

webpack 官網

從零開始搭建一個 Webpack 開發環境配置(附 Demo)

使用 Webpack 進行生產環境配置(附 Demo)

相關文章