目前的工作,開發團隊使用的框架是 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,只好想想其他解法了~)
思路大概是這樣:
- 使用 python script 讀取目前 flask application 的所有 endpoint 及 route path,並產出一 key-value 的 json 檔案。
- 透過 Gulp.js 的 gulp-replace-task 來置換所有
.js
檔案中的 route path 變數(寫的時候寫 endpoint 的名稱即可),產出最終 static file 的.js
都將是內含實際 route path 的檔案。 - 往後若有變更,只要重新跑 fab task 產生最新的
routes.json
,並再執行 gulp 產出.js
,便能確保路徑都是最新的。(deploy 時依序執行這些動作)
註:我在前端工作流程中原本就有透過 Gulp.js 來協助產出最終的 .js
檔案,包含 uglify
、include
、jshint
等等。您的情況並不一定適用,就選擇手邊有的工具來達成吧!
以下是實際的程式碼:
(假設您的 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,請自行參考。
大概就是這樣,希望有天找到更聰明的解法。