跳到主要內容

使用 Gulp Babel Browserify 打造 React Redux ES6 開發環境 (第一篇)

編譯 ES6 JSX 語法 | HTML 自動載入 CSS JS | 本地端伺服器

本文目的為介紹以 Gulp 打造 React Redux Router ES6 的開發及產品環境,以下將會說明所需的套件及設定方式,依建制步驟拆成五個篇章,關於 React Redux ES6 等相關知識不在此篇筆記介紹範圍。

第一篇 - 編譯 ES6 JSX 語法 | CSS JS 自動載入 | 本地端伺服器

第二篇 - 編譯 SASS | 語法偵錯 | 單元測試

  • 編譯 SASS 語法 | Gulp Sass
  • JavaScript 語法偵錯 | ESLint
  • 單元測試 | Jest

第三篇 - 環境變數 | 程式碼壓縮 | 檔案壓縮 | 輸出隨機檔名

  • 轉換開發及產品環境變數 | Browerify Dotenv Envify
  • JavaScript SASS 程式碼壓縮 | Gulp Sass Uglify
  • 產品環境下打包所有 JS CSS 檔案 | Gulp Useref
  • 輸出的 CSS JS 檔案附加不重複的檔名 | Gulp Rev

第四篇 - 彙整 Gulp task

  • 彙整 Gulp task - development
  • 彙整 Gulp task - production

第五篇 - 導入 Redux React-Router 架構

  • 導入 Redux Router 架構 | React Redux Router

前三篇會依序介紹每個功能及建置方式,第四篇會將零碎的 task 打包成一連串的動作,做成開發及產品環境的指令集,第五篇會把 react-redux 和 react-router 基本架構導入。

基本需求:已安裝 NodeJS 及了解如何使用 npm,並知道如何使用 Gulp 建立 task,第四篇有彙整 Gulp task 的部分,需要理解 NodeJS CommonJS 的模組語法。

編譯 JSX 及 ES6 語法 | Babel Browserify

React 使用 JSX 語法組織 HTML 架構,JSX 為 JavaScript 的 expression,然而現在的瀏覽器是無法讀懂它及 ES6 語法,因此我們需要編譯它,將它轉換為瀏覽器看得懂的語法,以下是使用 browserify babel 搭配 gulp 編譯 ES6 及 JSX 的設定內容:

$ npm install --save react react-dom
$ npm install --save-dev gulp babel-plugin-react-html-attrs babel-preset-es2015 babel-preset-react babel-preset-stage-0 babelify browserify watchify vinyl-buffer vinyl-source-stream

在專案的根目錄建立 .babelrc 檔案,設定 babel 的編譯規則。

// .babelrc 
{
  "presets": ["es2015", "react", "stage-0"],
  // react-html-attrs 可以把 JSX 中的 class 轉成 className、for 轉成 htmlFor (選用)
  "plugins": ["react-html-attrs"]
}

同樣在根目錄也建立 gulpfile.js,開始撰寫各個 gulp task

// gulpfile.js
var gulp       = require('gulp');
var browserify = require('browserify');
var babelify   = require('babelify');
var buffer     = require('vinyl-buffer');
var source     = require('vinyl-source-stream');
var watchify   = require('watchify');

// 印出錯誤,並發出事件完成,避免編譯過程因部分錯誤而讓 watch 中的 task 停止。
var handleError = function(err) {
  console.error(err); this.emit('end'); 
};

//  開始設定 browserify 編譯規則
var bundler = browserify({
  // react 進入點
  'entries': 'src/js/index.js', 
  // true 可以讓瀏覽器的 console 印出出現 bug 的行數是來自在哪一個檔案的哪一行,而不是最後編譯後的檔案的哪一行,方便在開發環境中 debug
  'debug': true,
  // watchify 必須加入以下兩個屬性
  'cache': {},
  'packageCache': {},
  // 使用 babelify 轉換:會讀取根目錄 .babelrc 檔案的 babel 轉換規則
  'transform': [babelify]
});

// 若是開發環境中,每次我們在撰寫 .js 檔案的時候,將整個 React 專案重新編譯會太慢,watchify 提供 update 事件,讓重新編譯的速度更快。
var watcher = watchify(bundler);

// browserify 依據設定進行編譯,並將檔案匯出至指定資料夾。
gulp.task('script', function() {
  return watcher
          .bundle()
          .on('error', handleError)
          .pipe(source('bundle.js'))
          .pipe(buffer())
          .pipe(gulp.dest('dist/js/'));
});

// 監聽程式碼的更新事件,以再次觸發編譯
watcher.on('update', function() {
  gulp.start('script');
});
src/js/index.js 為我們的 react 專案進入點,試著在 index.js 中寫些簡單的內容:
  // src/js/index.js
  import React from 'react';
  import ReactDOM from 'react-dom';

  ReactDOM.render(
    <h1>hello, world</h1>,
    document.getElementById("root")
  );
  

接者執行編譯:

$ gulp script
此時可以看見 script 這個 gulp task 已經開始並在幾秒後完成,因為我們有監聽 update 事件,所以 terminal 會持續等待我們更改內容,觸發編譯,我們可以試著更改 index.js 的內容並儲存,會顯示 Gulp 已經觸發自動重新編譯,如果這些動作都產生,代表我們已經成功建立編譯 ES6 JSX 的功能,而這部分只有編譯 JS 程式碼的部分,想要在瀏覽器執行這些程式碼需要 html 檔案引入編譯好的 script 及本地端的伺服器,接者兩節會緊接著補上。

HTML 自動載入 CSS JS | Bower Wiredep Gulp Inject

這裡建置的 task 會將 bower 安裝的套件和自己撰寫的檔案一起自動引入到 html 檔中,使用 wiredep 搭配 gulp-inject 就可以達到如此目的,不需要在手動管理 html 中的 script link 標籤。

$ npm install --save-dev gulp-inject wiredep

使用 bower 可以管理前端套件,如 jquery、bootstrap、sweetalert 等。如果 global 安裝 bower 可以像 npm 一樣,使用 bower init 開始設定 bower.json 內容,再使用 bower install <套件名稱> --save 安裝套件,預設會安裝在根目錄的 bower_components 資料夾中,在 bower.json 的 dependencies 屬性中加入剛剛按裝的套件及版本,記得在 .gitignore 中忽略 bower_components 這個資料夾,若要更改安裝的目錄位置,可以在根目錄新增 .bowerrc 檔案,加入更換的資料夾位置:

// .bowerrc
{
  "directory": "./dist/bower_components"
}

在 src/ 資料夾底下新增 index.html:

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Build React Redux with Gulp</title>
  <!-- bower:css -->
  <!-- endbower  -->
  <!-- inject:css -->
  <!-- endinject -->
</head>
<body>
<div id="root"></div>
  <!-- bower:js -->
  <!-- endbower -->
  <!-- inject:js -->
  <!-- endinject -->
</body>
</html>
wiredep 會判斷 html 檔案中
<!-- bower:css -->
<!-- endbower  -->

<!-- bower:js -->
<!-- endbower -->
的位置注入已安裝的 bower package css js。gulp-inject 會判斷 html 檔案中
<!-- inject:css -->
<!-- endinject -->

<!-- inject:js -->
<!-- endinject -->
的位置,將指定的檔案注入到該處 css js,接著是建立 gulp task 的設定:
// gulpfile.js
var inject  = require('gulp-inject');
var wiredep = require('wiredep').stream;

gulp.task('inject', function(){
  var injectSources = gulp.src(
    [
      'dist/js/*.js', 
      'dist/css/*.css'
    ], 
    {
      read: false
    }
  );

  return gulp
          .src('src/index.html')
          .pipe(wiredep({'ignorePath': '../dist'}))
          .pipe(inject(injectSources, {'ignorePath': '/dist'}))
          .pipe(gulp.dest('dist/'));
});

執行

$ gulp inject
就會把 src/index.html 的檔案根據設定的 inject 內容匯出至 dist/index.html,這時候就會發現目的資料夾已經把 bower 套件的 css js 跟 bundle.js 自動引入,接著我們需要一個開發用的伺服器。

備註:環境建好後才得知使用 bower 管理前端套件的部分其實 npm 也可以做到的,不同之處在於,以 npm 安裝套件之後,在使用到的檔案直接 import js css,而不是另外使用 script link 的方式直接在全域的環境下加入該套件的 css js 程式碼,個人認為是比較好的方式,可以避免污染全域變數,在後續建立測試環境時也不需要先宣告全域變數有哪些,未來有機會就會用這個方式取代 bower 管理這部分套件的方法了,詳細可以參閱:css-modules

建立開發用伺服器及自動重新載入 | Gulp Connect

使用 gulp-connect 可以建立一個開發用的本地伺服器,同時可以加上 livereload 功能,讓瀏覽器手動重新載入自動化,省去每次都要手動重新載入的麻煩。第四篇會把所寫過的 task 整理起來,搭配成自動重新編譯及自動引入編譯過後的檔案,然後瀏覽器自動重新載入。

$ npm install --save-dev gulp-connect
// gulpfile.js
var connect = require('gulp-connect');

// 建立 server: http://localhost:8080 (預設)
gulp.task('server', function(){
  connect.server({
    // 本地伺服器根目錄
    root: 'dist/',
    // 當呼叫 connect.reload 時,瀏覽器是否要重新載入
    livereload: true,
    // 用於 react router,所有 root folder 以下的 request 都會轉導到指定檔案
    fallback: 'dist/index.html'
  });
});

// 觸發 livereload 的 task,若已經跑過 server task,觸發這個 task 就會 reload
// 搭配 gulp.watch 來執行
gulp.task('reload', function() {
  return gulp.src('dist/index.html')
          .pipe(connect.reload());
});

執行

$ gulp server
就能在預設的: http://localhost:8080 中,以瀏覽器看到編譯過後的檔案執行結果。

大致上已經把 ES6 的編譯、html 自動載入執行及瀏覽器自動重新載入的功能做出來了,後續章節再慢慢統整這些 gulp task,讓整個開發/產品環境越來越完善。

留言

這個網誌中的熱門文章

Sublime Text 3 - 建立 React 開發環境

Sublime Text 一直是我很喜歡的前端編輯器,兼具輕量及可攜帶性一直是我很喜歡的特色,然而最近在開發 React 的時候,發現開發沒有特別設定的開發體驗實在不佳,於是在參考許多網路上大大們的文章後完成以下的開發環境需求,下面內容會一步步說明各個需求的設定過程,或許各位有更好的 package 清單,歡迎留言建議。 本篇所建立的開發環境 支援 ES6、JSX 語法高亮 | Support JSX Syntax Highlight 錯誤提示 | ES-Linter Emmet 支援 JSX 語法 | Enable Emmet while writing in JSX 自動完成語法 | React-Snippet 基本需求 Sublime 已經安裝 Package Manager 及 Emmet ,若已使用 SublimeLinter-jshint ,請在使用者設定的地方將他關掉。此外,NodeJS 也需要安裝。 Sublime Package List SublimeLinter SublimeLinter-contrib-eslint Babel Babel Snippets Node Package List eslint babel-eslint 1. 支援 ES6、JSX 語法高亮 使用 package manager 安裝 Babel 、 Babel Snippets ,再次開啟 .js 檔案時,點選 View -> Syntax -> Open all with current extension as... -> Babel -> JavaScript(Babel)。 若遇到想更換主題卻無法切換,可以在 ctrl + ` 的視窗中輸入 view.settings().erase("color_scheme") 。 2. 錯誤提示 npm 安裝 eslint 、 babel-eslint npm install eslint babel-eslint -g 使用 package manager 安裝 SublimeLinter 、 SublimeLinter-contrib-eslint 。 在...

使用 Gulp Babel Browserify 打造 React Redux ES6 開發環境 (第二篇)

編譯 SASS 語法 | JavaScript 語法偵錯 | 單元測試 本文目的為介紹以 Gulp 打造 React Redux Router ES6 的開發及產品環境,以下將會說明所需的套件及設定方式,依建制步驟拆成五個篇章,關於 React Redux ES6 Jest 等相關知識不在此篇筆記介紹範圍。 第一篇 - 編譯 ES6 JSX 語法 | CSS JS 自動載入 | 本地端伺服器 編譯 JSX 及 ES6 語法 | Babel Browserify HTML 自動載入 CSS JS | Bower Wiredep Gulp Inject 建立開發用伺服器及自動重新載入 | Gulp Connect 第二篇 - 編譯 SASS | 語法偵錯 | 單元測試 編譯 SASS 語法 | Gulp Sass JavaScript 語法偵錯 | ESLint 單元測試 | Jest 第三篇 - 環境變數 | 程式碼壓縮 | 檔案壓縮 | 輸出隨機檔名 轉換開發及產品環境變數 | Browerify Dotenv Envify JavaScript SASS 程式碼壓縮 | Gulp Sass Uglify 產品環境下打包所有 JS CSS 檔案 | Gulp Useref 輸出的 CSS JS 檔案附加不重複的檔名 | Gulp Rev 第四篇 - 彙整 Gulp task 彙整 Gulp task - development 彙整 Gulp task - production 第五篇 - 導入 Redux React-Router 架構 導入 Redux Router 架構 | React Redux Router 前三篇會依序介紹每個功能及建置方式,第四篇會將零碎的 task 打包成一連串的動作,做成開發及產品環境的指令集,第五篇會把 react-redux 和 react-router 基本架構導入。 基本需求:已安裝 NodeJS 及了解如何使用 npm ,並知道如何使用 Gul...

在送出 Ajax 時,將 cooke 值一並送出

在送出 Ajax 時,將 cookie 值一並送出 若 Ajax 送出的網址為相同 domain Ajax 會將瀏覽器的 Cookie 放在 header 的 Cookie 欄位以字串 "key1=value1; key2=value2; key3=value3" 的形式送出 若 Ajax 送出的網址為相不同 domain 預設行為不送 Cooke 這個 header。 若這種情況還是要送 cookie,則需要瀏覽器和伺服器互相配合 瀏覽器: 送 Ajax 時需要加入 withCredentials: true ,以使用 Axios 為例: axios.get('/someCorssOriginUrlIWantToSendCookie', { headers:{ withCredentials: true } }); 伺服器: 回應瀏覽器的 preflight request 時需要特別在 header 注意兩個設定: Access-Control-Allow-Origin 'http://some.domainname' 這種 CORS 情境不能為 *(wildcard),需要指定特定的 domain,且需要和 preflight request 相同。 Access-Control-Allow-Credentials true 註:這邊只針對送出 cookie 時要特別調整的兩個 header 的設定方式,由於 CROS 時,瀏覽器送出 Ajax 的 request 將格式依情況而定。例如 Ajax content-type 為 application/json,伺服器在回應 preflight request 時需要在 header 中加入 Access-Control-Allow-Headers Content-Type,表示伺服器接受這種 content-type 的 request,這部分不在這裡討論。 做完上面的設定,瀏覽器送出 Ajax 時,即會在 header 中加入 Cookie 欄位,值如同同 domain 的形式一樣。 參考網址: MDN