接了个新项目

栏目: 服务器 · Nginx · 发布时间: 6年前

内容简介:需求是这样的,需要做一套后台管理系统: 一个主系统,一个子系统,开发时间6个周。 前期开发有两个人, 再加一个人。说实话时间有点紧, 所以首先确定的还是 React 这一套, 即:

背景

最近接了个新项目, 遇到一些问题, 在这整理分享下。

前期规划

需求是这样的,需要做一套后台管理系统: 一个主系统,一个子系统,开发时间6个周。 前期开发有两个人, 再加一个人。

说实话时间有点紧, 所以 前期做好规划 就很重要。 实现先做一个规划,技术选型,文档分析,分页面, 有个大致的评估。

技术选型

首先确定的还是 React 这一套, 即: ReactReduxTypeScript , 样式管理 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 .

解决完打包的问题, 还有另一个问题, 就是本地开发的时候需要配置代理。

目前比较通用的做法有:

  1. devServer 配置 proxy
  2. 修改 host
  3. Nginx 做反向代理

// 也可以说只有两种。

我用的是1, 原因是比较灵活, 这个系统后面要发布到7个或者更多的国家, 改host 总归是不太优雅, 来回倒腾Nginx 又费时费力, 提个单大半天不批,不太方便。

后面又遇到的问题是登陆的时候需要请求一次csrftoken, 因为 domain 不匹配所以cookie 种不进来, 所以就改了下配置,代码见 devServer 部分,这个问题就解决了。

打包优化

初步做了个优化, 代码分包, 这个系统antd 用的比较多,代码体积, 和业务代码打在一个包里明显是不合适的,就简单分了一下:

接了个新项目

压缩后总体积900K。

接了个新项目

FCP 1s, 勉强还能接受, 后面有需要再做优化。

国际化实现

国际化用的是`react-intl`, 用起来就很简单了:

主要就两种形式:
  1. 直接翻译:

    <FormattedMessage id="xxx" />

  2. 需要特殊传, 比如 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 访问路径的问题:

需要配置的路径有:

  1. /index.html
  2. /admin
  3. /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号上测试,进度上问题不大。 后面有什么值得分享的问题再说。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First JavaScript Programming

Head First JavaScript Programming

Eric T. Freeman、Elisabeth Robson / O'Reilly Media / 2014-4-10 / USD 49.99

This brain-friendly guide teaches you everything from JavaScript language fundamentals to advanced topics, including objects, functions, and the browser’s document object model. You won’t just be read......一起来看看 《Head First JavaScript Programming》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换