在 Flask 中處理 JavaScript 裡指定 route path 的問題

Standard

目前的工作,開發團隊使用的框架是 Python 的 Flask;而身為小小前端的我在撰寫 Javascript 時,經常會遇到要在 JavaScript 中送 AJAX 到某個 route path 的實作,聽起來並不麻煩,但這個 route name 是在 Flask 中指定的,日後有可能更改,情況一多就改不完了,所以在 JavaScript 中寫死不是個好辦法。

記得之前在搭配 Rails 進行前端工作時,可以在 .js.erb 中寫 <% url = Demo::Application.routes.url_helpers %> 再用 url.某個 route name 來達成指定 route path 的目的。

可惜 Flask 好像沒有…,直接在 JavaScript 裡面寫 url_for 是沒用的(不考量更動結構,.js 依然都還是放在 static/ 目錄底下);比較直觀的作法大概是在 template (會引入該 .js 的那支 .html 檔案)裡面加上 js 變數,再透過 url_for 指定 route path 給它:

<script>
  var path = "{{ url_for('.test') }}";
</script>

嗯,是啊,在 template 中本來就可以使用 Jinja 語法的,只是我仍覺得這個解法不大漂亮。

所以想了些繞路的作法(若熟 Flask 的朋友路過,知道怎麼解才比較漂亮的話,請不吝賜教呀~),記錄於下:

其實會有這個想法是從 flask_util_js 這個專案來的,作者大概跟我有相似的煩惱 :P。但我手上處理的專案,要是把所有 route path 產生成一個 js 公開在前端,難免會有點疑慮,其實我只要特定幾支 route path 就好,所以看來還是不大合用。
(也許可以在後端撰寫 route 時加上選項來選擇是否要輸出,不過不大熟 python,只好想想其他解法了~)

思路大概是這樣:

  1. 使用 python script 讀取目前 flask application 的所有 endpoint 及 route path,並產出一 key-value 的 json 檔案。
  2. 透過 Gulp.js 的 gulp-replace-task 來置換所有 .js 檔案中的 route path 變數(寫的時候寫 endpoint 的名稱即可),產出最終 static file 的 .js 都將是內含實際 route path 的檔案。
  3. 往後若有變更,只要重新跑 fab task 產生最新的 routes.json,並再執行 gulp 產出 .js,便能確保路徑都是最新的。(deploy 時依序執行這些動作)

註:我在前端工作流程中原本就有透過 Gulp.js 來協助產出最終的 .js 檔案,包含 uglifyincludejshint 等等。您的情況並不一定適用,就選擇手邊有的工具來達成吧!

以下是實際的程式碼:

(假設您的 Gulp 都裝好了,也有既有的 gulpfile,這邊不再贅述)

先達成思路 1 所要做的事情 – 產出 route 的 json:

使用 fabric 來達成好了,請先透過 pip 安裝 fabric。接著,新增 fabric task(這段程式碼參考自 flask_util_js,特別感謝):

def generate_routes_json():
    import json
    rule_map = dict()

    for rule in app.url_map._rules:
        if rule.endpoint not in rule_map:
            rule_map[rule.endpoint] = rule.rule

    with open('routes.json', 'w') as outfile:
        json.dump(rule_map, outfile)

這段 fabric task 會幫我產出內含所有 route path 的 routes.json 檔案。

改寫既有的 gulp JavaScript task:

var gulp = require('gulp'),
    replace = require('gulp-replace-task');

gulp.task('js', function () {
  var fs = require('fs'),
      routes_file = './routes.json',
      routes_json;

  try {
    routes_json = require(routes_file);
  } catch (error) {
    if (error.code === 'MODULE_NOT_FOUND') {
      console.error('Please run "fab generate_routes_json" first.');
      process.nextTick(function () {
        process.exit(0);
      });
    }
  }

  return gulp.src("assets/js/*.js")
             .pipe(replace({
              patterns: [
                {
                  json: routes_json
                }
              ]
             }))
             .pipe(gulp.dest("static/js"));
});

gulp.task('default', ['js']);

在 .js 要改成這樣寫:

route path 的部份,改成 Flask endpoint 的名稱,加上 gulp-replce-task prefix 的符號 @@ (可自訂):

window.onload = function () {
  var r = new XMLHttpRequest();
  r.open("POST", "@@test", true);
  r.onreadystatechange = function () {
    if (r.readyState != 4 || r.status != 200) return;
    var result = JSON.parse(r.response);
    alert(result.result);
  };
  r.send();
};

最後 Gulp 產出會自動替換成正確的 route path。


詳細選項與原理請詳見官方文件囉~

另外 這邊是範例 repo,請自行參考。

大概就是這樣,希望有天找到更聰明的解法。

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *