最近接了個新項目, 遇到一些問題, 在這整理分享下。
需求是這樣的,須要作一套後臺管理系統: 一個主系統,一個子系統,開發時間6個周。 前期開發有兩我的, 再加一我的。javascript
說實話時間有點緊, 因此前期作好規劃
就很重要。 實現先作一個規劃,技術選型,文檔分析,分頁面, 有個大體的評估。css
首先肯定的仍是 React 這一套, 即: React
,Redux
,TypeScript
, 樣式管理 styled-components
, 國際化 react-intl
, 組件庫 antd
, 腳手架,本身配。 原本想圖省事用 CRA(create-react-app),後來以爲用rewired 重寫不太靈活, 並且有個小夥伴也想本身配,熟悉下 webpack , 仍是決定本身搭, 後面會把配置貼出來。html
和後端負責人討論以後決定把這一期的開發任務分紅三個小階段: P1, P2, P3java
P1 完成以後發佈, 先跑通主流程,P2 P3 繼續迭代功能。 node
P1 主要包括:react
主系統功能開發webpack
開發時間: 兩週nginx
壓力仍是有的,時間緊,任務重,並且是第一次帶人作項目, 好在心裏猶如一條老狗,一點都不慌。web
後面就進入了開發階段, 遇到了挺多問題。json
這一步你們就都很熟悉了,添加各類配置和打包。 由於主系統和子系統頁面風格都是同樣的, 不必分紅兩個系統, 把新開一個文件夾,裏面放子系統的頁面, 而後打成不一樣的包就能夠了。就有了以下配置:
// 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" },
根據不一樣的參數打包, 主系統打包到 dist/static
, 子系統打包到dist/adminstatic
.
解決完打包的問題, 還有另外一個問題, 就是本地開發的時候須要配置代理。
目前比較通用的作法有:
// 也能夠說只有兩種。
我用的是1, 緣由是比較靈活, 這個系統後面要發佈到7個或者更多的國家, 改host 總歸是不太優雅, 來回倒騰Nginx 又費時費力, 提個單大半天不批,不太方便。
後面又遇到的問題是登錄的時候須要請求一次csrftoken, 由於 domain 不匹配因此cookie 種不進來, 因此就改了下配置,代碼見 devServer 部分,這個問題就解決了。
初步作了個優化, 代碼分包, 這個系統antd 用的比較多,代碼體積, 和業務代碼打在一個包裏明顯是不合適的,就簡單分了一下:
壓縮後整體積900K。
FCP 1s, 勉強還能接受, 後面有須要再作優化。
國際化用的是`react-intl`, 用起來就很簡單了: 主要就兩種形式:
<FormattedMessage id="xxx" />
react-intl
提供了 injectIntl 方法能夠解決這個問題:<FormattedMessage /> 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 ( <input placeholder={intl.formatMessage({ id: "loginPage.username", defaultMessage: 'username'})}/> ) } } export default injectIntl(TestComponent);
傳入的 id, 是你本身定義的,若是有翻譯平臺的話, 能夠本身添加這些key:
在翻譯平臺完成翻譯後, 須要下載到本地, 須要手動下載, 感受很麻煩, 因而我就寫了個腳原本自動下載, 翻譯平臺更新後, 執行下 yarn i18n
就能夠更新過來了:
頁面字段的替換就按上面的兩種方法, 純粹的體力活, 沒什麼好說的。
功能開發完以後, 要發佈到測試環境, 中間要配置Nginx, 我這有個配置平臺, 加配置以後提單, 自動部署。
配置的時候仍是遇到一些問題的。
首先解決 index.html 訪問路徑的問題:
須要配置的路徑有:
首先看 /
和 /index.html
還須要配置環境和地區:
/admin
和 /admin/index.html
也同樣的配置。
不過須要注意的是, /
和 /admin
須要配置 try_files
:
/
:
/admin
:
對應生成的 conf 文件:
從字面上理解就是嘗試文件
,再結合環境理解就是嘗試讀取文件
, 那是想讀取什麼文件呢,讀取 靜態文件
.
$uri
, 這個是nginx的一個變量,存放着用戶訪問的地址. 好比:http://www.xxx.com/index.html
, 那麼$uri
就是 /index.html
$uri/
表明訪問的是一個目錄,好比:http://www.xxx.com/hello/test/
, 那麼$uri/
就是 /hello/test/
完整的解釋就是:
try_files
去嘗試到網站目錄讀取用戶訪問的文件, 若是第一個變量存在,就直接返回;
不存在繼續讀取第二個變量,若是存在,直接返回;不存在直接跳轉到第三個參數上。
至於爲何要配try_files
, 由於咱們的路由是基於browserHistory
的, 若是用 hashHistory
就不用配 try_files
。 你可能要問, 既然 hashHistory
能夠不用配 try_files
, 爲何你還要用browserHistory
呢?
多是由於, 加個/#/
看起來比較醜吧 :)