背景
近来接了个新项目, 碰到一些题目, 在这整顿分享下。
前期设计
需求是如许的,须要做一套背景治理体系: 一个主体系,一个子体系,开辟时刻6个周。 前期开辟有两个人, 再加一个人。
说实话时刻有点紧, 所以前期做好设计
就很重要。 完成先做一个设计,手艺选型,文档剖析,分页面, 有个大抵的评价。
手艺选型
起首肯定的照样 React 这一套, 即: React
,Redux
,TypeScript
, 款式治理 styled-components
, 国际化 react-intl
, 组件库 antd
, 脚手架,本身配。 本来想图费事用 CRA(create-react-app),厥后觉得用rewired 重写不太天真, 而且有个小伙伴也想本身配,熟习下 webpack , 照样决议本身搭, 背面会把设置贴出来。
开辟设计
和后端负责人议论以后决议把这一期的开辟使命分红三个小阶段: P1, P2, P3
P1 完成以后宣布, 先跑通主流程,P2 P3 继承迭代功用。
P1 重要包含:
- 开辟环境搭建
- test环境资本要求
- Nginx 设置
主体系功用开辟
- 三个功用模块开辟
- 上岸注册流程
- 子体系两个模块的开辟
开辟时刻: 两周
压力照样有的,时刻紧,使命重,而且是第一次带人做项目, 幸亏心田如同一条老狗,一点都不慌。
背面就进入了开辟阶段, 碰到了挺多题目。
进入开辟
搭建开辟环境
这一步人人就都很熟习了,增加种种设置和打包。 由于主体系和子体系页面作风都是一样的, 没必要分红两个体系, 把新开一个文件夹,内里放子体系的页面, 然后打成差别的包就能够了。就有了以下设置:
// 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
.
处理完打包的题目, 另有另一个题目, 就是当地开辟的时刻须要设置代办。
现在比较通用的做法有:
- devServer 设置 proxy
- 修正 host
- Nginx 做反向代办
// 也能够说只要两种。
我用的是1, 原因是比较天真, 这个体系背面要宣布到7个或许更多的国度, 改host 总归是不太文雅, 往返捣腾Nginx 又费时辛苦, 提个单大半天不批,不太轻易。
背面又碰到的题目是上岸的时刻须要要求一次csrftoken, 由于 domain 不婚配所以cookie 种不进来, 所以就改了下设置,代码见 devServer 部份,这个题目就处理了。
打包优化
开端做了个优化, 代码分包, 这个体系antd 用的比较多,代码体积, 和营业代码打在一个包里显著是不合适的,就简朴分了一下:
紧缩后总体积900K。
FCP 1s, 委曲还能接收, 背面有须要再做优化。
国际化完成
国际化用的是`react-intl`, 用起来就很简朴了:
重要就两种情势:
- 直接翻译:
<FormattedMessage id="xxx" />
- 须要特别传, 比方 placeholder, Modal 的title等,假如直接用1 的体式格局会显现一个[object Object] ,幸亏
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 设置
功用开辟完以后, 要宣布到测试环境, 中心要设置Nginx, 我这有个设置平台, 加设置以后提单, 自动布置。
设置的时刻照样碰到一些题目的。
起首处理 index.html 接见途径的题目:
须要设置的途径有:
- /
- /index.html
- /admin
- /admin/index.html
起首看 /
和 /index.html
还须要设置环境和区域:
/admin
和 /admin/index.html
也一样的设置。
不过须要注重的是, /
和 /admin
须要设置 try_files
:
/
:
/admin
:
对应天生的 conf 文件:
什么是try_files
从字面上明白就是尝试文件
,再连系环境明白就是尝试读取文件
, 那是想读取什么文件呢,读取 静态文件
.
$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
呢?
能够是由于, 加个/#/
看起来比较丑吧 :)
未完待续, 延续更新..
- 5.21 现在处于P3阶段, 重要功用开辟完成,设计5.27号上测试,进度上题目不大。 背面有什么值得分享的题目再说。