React 教學 - 從零開始的 React 生活 Part 1 之後,本篇將進入如何運用前端開發工具讓開發 React 應用更加有效率。

本文環境

  • Chrome 81
  • React 16.13.1
  • nodejs 12.16.1
  • npm 6.13.4

nodejsnpm 請至 nodejs 官方網站 下載安裝檔並進行安裝。

JSX

React 教學 - 從零開始的 React 生活 Part 1 我們學會使用 React.createElement() 直接在頁面上渲染一個 element ,例如:

const element = React.createElement(
  'h1',
  {className: 'heading'},
  'Hello, React!'
);

不過現代的網頁應用其實相當複雜,如果單純用 React.createElement() 撰寫 React 將會十分難以閱讀。

例如以下 HTML 結構:

<div class="a">
    <div class="b">
        <div class="c">Hi</div>
    </div>
</div>

若單純以 React.createElement() 撰寫將會變成以下的程式碼,讓人覺得難以親近:

const element = React.createElement(
  'div',
  {className: 'a'},
  React.createElement(
    'div',
    {className: 'b'},
    React.createElement(
      'div',
      {className: 'c'},
      'Hi'
    )
  )
);

這時候就該使用 JSX 來幫助我們開發 React 。

若使用 JSX 的話,前述難以閱讀的程式碼將會變成:

const element = (
  <div className="a">
    <div className="b">
      <div className="c">
      Hi
      </div>
    </div>
  </div>
)

頓時覺得人生充滿希望!

事實上 JSX 是一種 Javascript 語法的擴充,也是 React 官方推薦與 React 搭配開發的利器:

It is called JSX, and it is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like.

也由於 JSX 是一種 Javascript 語法的擴充,因此一般都需要透過 BABEL 編譯器將含有 JSX 語法的 React 程式編譯成現代瀏覽器能夠執行的 Javascript 後才能正常執行。

p.s. 此處的編譯並非指將 Javascript 編譯成執行檔,而是將各種較新版本的 Javascript (ES6, ES7…) 轉換成現代瀏覽器能夠執行的版本

因此,接下來將利用網頁前端著名的 Webpack 開發工具,打造能夠使用 JSX 舒服地進行 React 開發的環境。

Webpack

利用 Webpack 進行 React 應用的開發,剛開始肯定會感到不夠直覺,但久而久之就會習慣。

我們從頭開始跑一次。

首先,用以下指令建立 1 個專案資料夾,並且用 npm init 指令新增 1 個 package.jsonnpm 可以對專案進行套件管理。

$ mkdir react-project
$ cd react-project
$ npm init

npm init 會顯示幾個問項,可以都不填,也可以隨意填。

成功的話,會出現 1 個檔案 pacakge.json 在專案資料夾中。

p.s. 關於 package.json 本篇將不會做太多介紹

接著安裝 Webpack & Babel & React preset ,好讓 Webpakck 具有編譯 React 以及 JSX 的能力:

$ npm install --save-dev webpack
$ npm install --save-dev @babel/core @babel/preset-react @babel/preset-env babel-loader

p.s. 上述的 preset 用途,可以閱讀 官方文件

接著安裝 React ,讓我們不需透過 CDN 取得 React ,變成可以用 import 的方式使用 React 。

$ npm install --save react react-dom

成功的話,可以看到多了 1 個 package-lock.json 檔案,裡面記錄著專案所有的相依套件(packages) ,一般並不需要特別理會,放著即可。

package.json 也會增加我們剛剛安裝的 3 個套件:

{
  "name": "react-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/preset-env": "^7.9.6",
    "@babel/preset-react": "^7.9.4",
    "webpack": "^4.43.0",
    "babel-loader": "^8.1.0"
  }
}

再來新增 1 個 webpack.config.js 的設定檔,讓 Webpack 能夠載入編譯 React & JSX 的設定, webpack.config.js 檔案內容如下:

const path = require("path");

module.exports = {
  entry: {
    app: "./src/app.jsx",
  },
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].js",
    publicPath: "/"
  },
  resolve: {
    extensions: [".jsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              "@babel/preset-env"
            ]
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: { presets: ["@babel/preset-env"] }
        }
      },
    ]
  },
}

礙於篇幅關係,就不多詳述關於 Webpack 設定檔的一切,僅會針對重點部分進行解說。

首先談到 entry 的部分,我們此處設定 app: "./src/app.jsx" ,等於告訴 Webpack 幫我們編譯 ./src/app.jsx ,並按照 output 的設定輸出至 dist/ 資料夾中,其中 [name] 指的是 app: "./src/app.jsx" 中的鍵值 app ,所以如果將該設定改成 index: "./src/app.jsx" 的話, Webpack 會幫忙把 ./src/app.jsx 編譯成 index.js 後輸出至 dist/ 資料夾中。

設定檔剩下的部分就是針對 .jsx.js 檔案的編譯設定,基本上就是針對不同的副檔名設定不同的編譯方式,此處不多作解釋,照抄即可。

接著把以下含有 JSX 語法的 React 範例貼到 src/app.js 中:

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

class App extends React.Component {
  render() {
    return (
      <div>React loves JSX!</div>
    )
  }
}

ReactDOM.render(<App />, document.querySelector('#app'));

至此,我們專案資料夾應該長這個樣子:

.
├── package-lock.json
├── package.json
├── src
│   └── app.jsx
└── webpack.config.js

接著輸入以下指令編譯看看吧!

$ webpack

成功的話,會出現一個 dist/ 資料夾,裡面會有編譯過的 app.js

.
├── dist
│   └── app.js
├── package-lock.json
├── package.json
├── src
│   └── app.jsx
└── webpack.config.js

打開 app.js 看的話,可以發現裡面有一大堆的 Javascript 程式碼,內容都是經過壓縮的,包含 React 套件等也都一起被編譯在內,所以 app.js 其實是可以獨立運作的,並不需要在網頁內引入其他套件。

不過單只有 app.js 還不夠,我們還需要一個 index.html 讓我們可以在瀏覽器看到 app.js 的執行結果。

HtmlWebpackPlugin

本章將利用 Webpack 的 plugin HtmlWebpackPlugin 幫我們產生 index.html 方便我們看到 app.js 的執行結果。

首先透過 npm 安裝該 plugin :

$ npm install --save-dev html-webpack-plugin

接著更動 webpack.config.js ,添加 plugin 的設定:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    app: "./src/app.jsx",
  },
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].js",
    publicPath: "/"
  },
  resolve: {
    extensions: [".jsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              "@babel/preset-env"
            ]
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: { presets: ["@babel/preset-env"] }
        }
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "./src/index.html'
    })
  ],
}

可以看到上述設定多了:

  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "./src/index.html",
    })
  ],

其作用在於告訴 HtmlWebpackPlugin 參考 src/index.html 當樣板,幫我們產生 index.html

再來新增樣板 src/index.htmlHtmlWebpackPlugin 使用:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
 </head>
 <body>
  <div id="app"></div>
 </body>
</html>

完成之後,我們用開發模式編譯一次試試!

p.s. 用 development 模式可以讓 Webpack 不要用壓縮的方式產生檔案,方便我們打開檔案閱讀

$ webpack --mode development

成功的話,就會看到 dist/ 資料夾內出現 index.html 囉!

以文字編輯器打開 index.html 的話,將會看到:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
 </head>
 <body>
  <div id="app"></div>
 <script src="/app.js"></script></body>
</html>

HtmlWebpackPlugin 自動幫我們把 <script src="/app.js"></script> 給添加到 HTML 內了。

再來用瀏覽器打開 index.html 之後,將會發現一片空白,可以看到 console 出現 GET file:///app.js net::ERR_FILE_NOT_FOUND

原因在於瀏覽器讀取 index.html 時,試圖至檔案系統 file:/// 尋找 app.js

所以我們需要對 dist/index.html 做些修正,將 <script src="/app.js"></script> 改成 <script src="./app.js"></script> ,讓瀏覽器從當前資料夾開始找 app.js ,就可以修正錯誤。

成功的話,就可以看到 React loves JSX! 的文字出現在網頁囉!

不過每次都要修改 dist/index.html 肯定是一種麻煩,接下來我們就用 Webpack 的 devServer 來解決這問題!

Webpack devServer

畢竟,編譯好的網頁應用最後還是會透過網頁伺服器提供服務,那麼開發時使用網頁伺服器模擬真實環境,也會相當貼近真實的使用情況,更可以避免瀏覽器至檔案系統中尋找檔案的問題。

幸好, Webpack 也提供 dev server 的功能供我們使用,同樣須先安裝套件並修改 webpack.config.js

透過 npm 安裝 webpack-dev-server

$ npm install --save-dev webpack-cli webpack-dev-server

修改 webpack.config.js 添加 devServer 的設定:

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    app: "./src/app.jsx",
  },
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].js",
    publicPath: "/"
  },
  resolve: {
    extensions: [".jsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              "@babel/preset-env"
            ],
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: { presets: ["@babel/preset-env"] }
        }
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My React App',
      filename: 'index.html',
      template: "./src/index.html",
    })
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: false,
    port: 9000
  },
}

上述添加:

  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: false,
    port: 9000
  },

該設定將 dev server listen 在 9000 port ,並且將 dist/ 資料夾作為網頁伺服器提供服務的資料夾。

最後啟動 dev server 試試看:

$ node_modules/.bin/webpack-dev-server

成功的話,畫面將會出現:

ℹ 「wds」: Project is running at http://localhost:9000/
ℹ 「wds」: webpack output is served from /
...

我們可以用瀏覽器打開網址 http://localhost:9000/ 就可以看到 React 專案正確執行並顯示 React loves JSX!

以上就是本篇利用 Webpack 編譯 React 與 JSX 的過程。

下一篇將回到 React 的部分,將 React 教學 - 從零開始的 React 生活 Part 1 中的範例改成 JSX 形式的 React 應用。

Happy Coding!

References

https://reactjs.org/

https://webpack.js.org/