摘要:背景最近接了個新項目,遇到一些問題,在這整理分享下。你可能要問,既然可以不用配為什么你還要用呢可能是因為,加個看起來比較丑吧未完待續(xù),持續(xù)更新目前處于階段,主要功能開發(fā)完成,計劃號上測試,進(jìn)度上問題不大。
背景
最近接了個新項目, 遇到一些問題, 在這整理分享下。前期規(guī)劃
需求是這樣的,需要做一套后臺管理系統(tǒng): 一個主系統(tǒng),一個子系統(tǒng),開發(fā)時間6個周。 前期開發(fā)有兩個人, 再加一個人。
說實話時間有點緊, 所以前期做好規(guī)劃就很重要。 實現(xiàn)先做一個規(guī)劃,技術(shù)選型,文檔分析,分頁面, 有個大致的評估。
技術(shù)選型首先確定的還是 React 這一套, 即: React,Redux,TypeScript, 樣式管理 styled-components, 國際化 react-intl, 組件庫 antd, 腳手架,自己配。 本來想圖省事用 CRA(create-react-app),后來覺得用rewired 重寫不太靈活, 而且有個小伙伴也想自己配,熟悉下 webpack , 還是決定自己搭, 后面會把配置貼出來。
開發(fā)計劃和后端負(fù)責(zé)人討論之后決定把這一期的開發(fā)任務(wù)分成三個小階段: P1, P2, P3
P1 完成之后發(fā)布, 先跑通主流程,P2 P3 繼續(xù)迭代功能。
P1 主要包括:
開發(fā)環(huán)境搭建
test環(huán)境資源申請
Nginx 配置
主系統(tǒng)功能開發(fā)
三個功能模塊開發(fā)
登陸注冊流程
子系統(tǒng)兩個模塊的開發(fā)
開發(fā)時間: 兩周
壓力還是有的,時間緊,任務(wù)重,而且是第一次帶人做項目, 好在內(nèi)心猶如一條老狗,一點都不慌。
后面就進(jìn)入了開發(fā)階段, 遇到了挺多問題。
進(jìn)入開發(fā) 搭建開發(fā)環(huán)境這一步大家就都很熟悉了,添加各種配置和打包。 因為主系統(tǒng)和子系統(tǒng)頁面風(fēng)格都是一樣的, 沒必要分成兩個系統(tǒng), 把新開一個文件夾,里面放子系統(tǒng)的頁面, 然后打成不同的包就可以了。就有了如下配置:
// webpack.config.js const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") const webpack = require("webpack") const fs = require("fs") const lessToJS = require("less-vars-to-js") const { NODE_ENV } = process.env const isAdminApp = process.env.APP_TYPE === "admin" const getBaseurl = () => { switch (process.env.ENV) { case "id": return "https://xxx.test.shopee.co.id" default: return "" } } const plugins = [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "template.html"), title: isAdminApp ? "WMS LITE ADMIN" : "WMS LITE", }), new webpack.DefinePlugin({ __BASEURL__: JSON.stringify(getBaseurl()), }), new webpack.IgnorePlugin(/^./locale$/, /moment$/) ] if (NODE_ENV !== "production") { plugins.push(new webpack.SourceMapDevToolPlugin({})) } const themeVariables = lessToJS(fs.readFileSync(path.resolve(__dirname, "./assets/antd-custom.less"), "utf8")) const port = isAdminApp ? 9527 : 8080 module.exports = { entry: [ "@babel/polyfill", isAdminApp ? "./admin/index.js" : "./pages/index.js" ], output: { filename: isAdminApp ? "admin.[hash:8].js" : "main.[hash:8].js", path: path.resolve(__dirname, isAdminApp ? "dist/adminstatic" : "dist/static"), publicPath: isAdminApp ? "/admin/" : "/", }, mode: NODE_ENV, devtool: false, plugins, module: { rules: [ { test: /.(js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", }, }, { test: /.less$/, use: [ { loader: "style-loader", }, { loader: "css-loader", }, { loader: "less-loader", options: { javascriptEnabled: true, sourceMap: true, modifyVars: themeVariables, }, } ], }, { test: /.css$/, use: [ { loader: "style-loader", }, { loader: "css-loader", } ], }, { type: "javascript/auto", test: /.mjs$/, use: [], }, { test: /.(png|jpg|gif|svg)$/i, use: [ { loader: "url-loader", options: { limit: 8192, }, } ], } ], }, optimization: { runtimeChunk: { name: "manifest", }, splitChunks: { chunks: "all", minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, cacheGroups: { vendor: { test: /[/]node_modules/, filename: "vendor.[chunkhash:8].js", enforce: true, priority: 5, }, antd: { test: /[/]node_modules[/]antd[/]/, filename: "antd.[chunkhash:8].js", priority: 10, }, antdIcons: { test: /[/]node_modules[/]@ant-design[/]/, filename: "antd-icons.[chunkhash:8].js", priority: 15, }, styles: { test: /.(scss|css)$/, filename: "styles.[hash:8].css", minChunks: 1, reuseExistingChunk: true, enforce: true, priority: 20, }, }, }, }, devServer: { historyApiFallback: isAdminApp ? { rewrites: [{ from: /.*/g, to: "/admin/index.html", }], } : true, hot: true, port, proxy: [{ context: ["/admin/api", "/api"], target: "https://gudangku.test.shopee.co.id", changeOrigin: true, onProxyRes(proxyRes, _, res) { const cookies = proxyRes.headers["set-cookie"] || [] const re = /domain=[w.]+;/i const newCookie = cookies.map(cookie => cookie.replace(re, "Domain=localhost;")) res.writeHead(200, { ...proxyRes.headers, "set-cookie": newCookie, }) }, }], }, }
// package.json "scripts": { "start": "NODE_ENV=development APP_TYPE=main webpack-dev-server", "build": "NODE_ENV=production APP_TYPE=main webpack", "start:admin": "NODE_ENV=development APP_TYPE=admin webpack-dev-server", "build:admin": "NODE_ENV=production APP_TYPE=admin webpack", "lint": "eslint ./ --ext js", "i18n": "node i18n/index.js" },
根據(jù)不同的參數(shù)打包, 主系統(tǒng)打包到 dist/static, 子系統(tǒng)打包到dist/adminstatic.
解決完打包的問題, 還有另一個問題, 就是本地開發(fā)的時候需要配置代理。
目前比較通用的做法有:
devServer 配置 proxy
修改 host
Nginx 做反向代理
// 也可以說只有兩種。
我用的是1, 原因是比較靈活, 這個系統(tǒng)后面要發(fā)布到7個或者更多的國家, 改host 總歸是不太優(yōu)雅, 來回倒騰Nginx 又費時費力, 提個單大半天不批,不太方便。
后面又遇到的問題是登陸的時候需要請求一次csrftoken, 因為 domain 不匹配所以cookie 種不進(jìn)來, 所以就改了下配置,代碼見 devServer 部分,這個問題就解決了。
打包優(yōu)化初步做了個優(yōu)化, 代碼分包, 這個系統(tǒng)antd 用的比較多,代碼體積, 和業(yè)務(wù)代碼打在一個包里明顯是不合適的,就簡單分了一下:
壓縮后總體積900K。
FCP 1s, 勉強還能接受, 后面有需要再做優(yōu)化。
國際化實現(xiàn)國際化用的是`react-intl`, 用起來就很簡單了: 主要就兩種形式:
直接翻譯:
需要特殊傳, 比如 placeholder, Modal 的title等,如果直接用1 的方式會顯示一個[object Object] ,好在react-intl 提供了 injectIntl 方法可以解決這個問題:
is a component which cannot be placed to placeholder which expects a raw String.
用法:
import {injectIntl} from "react-intl"; class TestComponent extends React.Component{ render(){ const { intl } = this.props; return ( ) } } export default injectIntl(TestComponent);
傳入的 id, 是你自己定義的,如果有翻譯平臺的話, 可以自己添加這些key:
在翻譯平臺完成翻譯后, 需要下載到本地, 需要手動下載, 感覺很麻煩, 于是我就寫了個腳本來自動下載, 翻譯平臺更新后, 執(zhí)行下 yarn i18n 就可以更新過來了:
頁面字段的替換就按上面的兩種方法, 純粹的體力活, 沒什么好說的。
Nginx 配置功能開發(fā)完之后, 要發(fā)布到測試環(huán)境, 中間要配置Nginx, 我這有個配置平臺, 加配置之后提單, 自動部署。
配置的時候還是遇到一些問題的。
首先解決 index.html 訪問路徑的問題:
需要配置的路徑有:
/
/index.html
/admin
/admin/index.html
首先看 / 和 /index.html
還需要配置環(huán)境和地區(qū):
/admin 和 /admin/index.html 也一樣的配置。
不過需要注意的是, / 和 /admin 需要配置 try_files :
/ :
/admin :
對應(yīng)生成的 conf 文件:
什么是try_files從字面上理解就是嘗試文件,再結(jié)合環(huán)境理解就是嘗試讀取文件, 那是想讀取什么文件呢,讀取 靜態(tài)文件.
$uri, 這個是nginx的一個變量,存放著用戶訪問的地址. 比如:http://www.xxx.com/index.html, 那么$uri就是 /index.html
$uri/ 代表訪問的是一個目錄,比如:http://www.xxx.com/hello/test/, 那么$uri/就是 /hello/test/
完整的解釋就是:
try_files 去嘗試到網(wǎng)站目錄讀取用戶訪問的文件, 如果第一個變量存在,就直接返回;
不存在繼續(xù)讀取第二個變量,如果存在,直接返回;不存在直接跳轉(zhuǎn)到第三個參數(shù)上。
至于為什么要配try_files , 因為我們的路由是基于browserHistory的, 如果用 hashHistory 就不用配 try_files。 你可能要問, 既然 hashHistory 可以不用配 try_files, 為什么你還要用browserHistory 呢?
可能是因為, 加個/#/ 看起來比較丑吧 :)
未完待續(xù), 持續(xù)更新..5.21 目前處于P3階段, 主要功能開發(fā)完成,計劃5.27號上測試,進(jìn)度上問題不大。 后面有什么值得分享的問題再說。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/114666.html
摘要:作為一個老前端給新人們的一些關(guān)于開發(fā)流程上的建議拒絕和設(shè)計稿沒有封板就要求排期,和產(chǎn)品設(shè)計充分溝通。雙方還是要找到平衡點確保產(chǎn)品質(zhì)量和上線時間點的前提下,前端的開發(fā)難度和工作量是合理的。 作為一個老前端給新人們的一些關(guān)于開發(fā)流程上的建議 1. 拒絕PRD和設(shè)計稿沒有封板就要求排期,和產(chǎn)品設(shè)計充分溝通。 前端開發(fā)經(jīng)常會遇到這樣的情況,項目經(jīng)理莫名奇妙發(fā)了個會議邀請,然后一股腦把產(chǎn)品下階段...
摘要:因為網(wǎng)站建設(shè)一般項目比較小,我一個人是可以完成前后端開發(fā)的,如果做成成品當(dāng)然得加上小蘇設(shè)計師。關(guān)鍵詞選擇因為面向的是單個城市業(yè)務(wù),在城市選擇上猶豫了不少時間,首先得是一個大城市,客戶量足,當(dāng)時我在北京,小蘇是在一個省會城市。 我技術(shù)之外的資本是零,如果你也是這樣,那這篇文章適合你! 這是我的故事之一,希望對你有啟發(fā)。如果你每天下班后就是躺在床上刷刷斗音,看看微博。但是又總想擺脫黑暗迷亂...
摘要:因為網(wǎng)站建設(shè)一般項目比較小,我一個人是可以完成前后端開發(fā)的,如果做成成品當(dāng)然得加上小蘇設(shè)計師。關(guān)鍵詞選擇因為面向的是單個城市業(yè)務(wù),在城市選擇上猶豫了不少時間,首先得是一個大城市,客戶量足,當(dāng)時我在北京,小蘇是在一個省會城市。 我技術(shù)之外的資本是零,如果你也是這樣,那這篇文章適合你! 這是我的故事之一,希望對你有啟發(fā)。如果你每天下班后就是躺在床上刷刷斗音,看看微博。但是又總想擺脫黑暗迷亂...
摘要:因為網(wǎng)站建設(shè)一般項目比較小,我一個人是可以完成前后端開發(fā)的,如果做成成品當(dāng)然得加上小蘇設(shè)計師。關(guān)鍵詞選擇因為面向的是單個城市業(yè)務(wù),在城市選擇上猶豫了不少時間,首先得是一個大城市,客戶量足,當(dāng)時我在北京,小蘇是在一個省會城市。 我技術(shù)之外的資本是零,如果你也是這樣,那這篇文章適合你! 這是我的故事之一,希望對你有啟發(fā)。如果你每天下班后就是躺在床上刷刷斗音,看看微博。但是又總想擺脫黑暗迷亂...
閱讀 2894·2021-11-24 09:39
閱讀 1157·2021-11-02 14:38
閱讀 4141·2021-09-10 11:26
閱讀 2743·2021-08-25 09:40
閱讀 2303·2019-08-30 15:54
閱讀 477·2019-08-30 10:56
閱讀 2737·2019-08-26 12:14
閱讀 3211·2019-08-26 12:13