09月18, 2018

项目代码分享(1)

来这边这个项目组快2个月了。一开始了解到项目不是react,而是jquery,甚至要兼容IE8,我内心是比较抗拒的。但慢慢发现其实项目架构层面还是做的比较牛的,所以想写一些文章记录一下。

eslint

这个看似比较容易,但其实还是有一些可以学的东西。例如写一个自己的eslint plugin

在项目中eslint一般会有这几个东西:

  • npm包
  • .eslintrc
  • .eslintignore

npm包

下面是常见的eslint相关npm包:

  • babel-eslint
  • eslint
  • eslint-config-airbnb
  • eslint-loader
  • eslint-plugin-import
  • eslint-plugin-jsx-a11y
  • eslint-plugin-mocha
  • eslint-plugin-react

自定义eslint插件:eslint-plugin-你指定的插件名称,比如下面的:

{
     "eslint-plugin-its": "file:./infrastructure/eslint-plugin-its",
}

也许有人好奇,自定义的eslint可以写啥?比如:

alt

拿两个大家看得懂的rule来说:function-length是用来限制函数体的代码行数的(比如说最多30行,超过30行,警告或者报错),comment-percentage是用来计算单个文件的注释百分比(比如要约束注释起码10%)。

eslintrc

常见配置如下:

{
    "extends": "airbnb",
    "parser": "babel-eslint",
    "env": {
      "mocha": true,
      "browser": true,
      "node": true
    },
    "plugins": [
      "its" // 这里加自己的eslint插件
    ],
    "rules": {
      "jsx-a11y/no-static-element-interactions": 0,
      "react/sort-comp": 0,
      "react/forbid-prop-types": 0,
      "react/no-array-index-key": 0,
      "class-methods-use-this": 0,
      "no-plusplus": 0,
      "no-mixed-operators": 0,
      "react/no-danger": 0,
      "import/no-extraneous-dependencies": 0,
      "import/no-unresolved": 0,
      "import/extensions": 0,
      "react/no-multi-comp": 0,
      "no-underscore-dangle": 0,
      "comma-dangle": 0,
      "no-multiple-empty-lines": 0,
      "no-param-reassign": 0,
      "no-lonely-if": 0,
      "global-require": 0,
      "import/no-dynamic-require": 0,
      "its/function-length": 1,
      "its/comment-percentage": 1,
    }
}

这里有些规则没有前缀(像react/import/its)这种,是因为它们是默认的rule,plugin里面的rule都得带上前缀。

alt

.eslintignore

顾名思义,就是eslint忽略的文件,如:

node_modules/
dist/

另外我们还需要知道,如何通过注释关闭规则。

关闭某块代码的检验

/* eslint-disable */

alert('foo');

/* eslint-enable */

关闭代码块某些规则的检验

/* eslint-disable no-alert, no-console */

alert('foo');
console.log('bar');

/* eslint-enable no-alert, no-console */

彻底关闭对某个文件的检验

/* eslint-disable */

alert('foo');

关闭对某行代码的检验

alert('foo'); // eslint-disable-line

// 或者

// eslint-disable-next-line
alert('foo');

框架介绍

web项目是使用了一套简单的MVVM实现+ jQuery & artTemplate + its-component(基于jQuery的组件) + infrastructure(webpack构建) 来实现了整个开发体系。

同时,提供了公共模块 its-common 来将一些统一的行为(比如request.js,i18n.js)进行归纳。让业务在大部分情况下,无需再进行此部分的开发。

基本的理念是:

  • 通过 简单的MVVM 来实现对于界面中的基本结构的控制,比如元素的显示隐藏,文案控制等

  • jQuery & artTemplate 用于内容生成和节点操作,处理需要动态改变的部分

  • its-component(基于jQuery的组件) 用于实现整个新的交互语言,减少最基本的元素组件的开发工作

  • infrastructure(webpack构建) 用来实现开发构建,让开发上能够通过简单的方式去组合代码,并且能够用最新的 ES 语法去提效整体开发。

页面开发流程

每个页面模块都由以下几个部分组成:

  • index.js (入口,通常在框架层面处理)
  • data.js (数据,可以简单理解成react中的state)
  • events.js (模块的初始化事件处理)
  • bindings.js (模块里的事件绑定处理)
  • index.tpl (模块的html代码)
  • methods.js (模块的方法)
  • style.scss (模块的样式文件)

框架层面处理的index.js模板文件:

import Tax from 'Tax'; 
{{@ componentImport}}

import '{{@ relativePath}}/src/layout/{{@ layoutName}}';

import beforePageMount from '{{@ relativePath}}/src/hook/beforePageMount';
import afterPageMount from '{{@ relativePath}}/src/hook/afterPageMount';

import data from '{{@ relativePath}}/src/page/{{@ pageDir}}/data';
import events from '{{@ relativePath}}/src/page/{{@ pageDir}}/events';
import tpl from '{{@ relativePath}}/src/page/{{@ pageDir}}/index.tpl';

import '{{@ relativePath}}/src/page/{{@ pageDir}}/style.scss';

beforePageMount();

Tax.mount({
  name: 'Page/{{@ pageDir}}',
  data,
  events,
  tpl,
}, false, {}, [{{@ componentsListString}}]);

afterPageMount();

上面的代码并不是很难读,页面主流程是:

  • beforePageMount() // 加载hook中的页面加载之前的模块,比如可以绑定一些事件
  • Tax.mount // 加载页面
  • afterPageMount() // 页面加载完要做的一些事

其中Tax是一个简单的mvvm实现,我们来粗略看一下:

alt

alt

无非就是收集依赖(比如说自定义的标签,像vue里面的v-ifv-text),然后通过data及setState进行dom的小范围更新。

国际化

一般会有两类,页面级别和通用级别的。

页面级别的,通过node来进行处理,拿到页面目录下面的lang.yml,然后转成json,再用window来挂载一个变量,写到某个文件去。

en:
 page_title: index
zh-CN:
  page_title: 首页
const yaml = require('js-yaml');
const jsonString = fs.readFileSync(langPath, 'UTF-8');
const json = yaml.load(jsonString);

通用级别的,放到一个i18n.js中进行处理,通过yaml-loader

在项目中要使用国际化的话,得到上面的json结构还是不够的,因为结构中多了一层语言,比如en或者zh之类的,所以肯定还要处理一下。

const Locale = function getLocal(options = {}) {
  const defaultOptions = {
    defaultLocale: '', // if not set, use navigator language
    queryField: 'locale',
    cookieField: 'locale',
    cookieMaxAge: 365,
    storageList: ['cookie'],
    get() {
      // search query, then cookie, then defaultLocal, then navigator
      const { query } = urlData;
      if (query.locale) {
        return query.locale;
      }

      const cookieRs = cookie.get('locale');
      if (cookieRs) {
        return cookieRs;
      }

      if (this.defaultLocal) {
        return this.defaultLocal;
      }

      return navigator.languages
        ? navigator.languages[0]
        : (navigator.language || navigator.userLanguage || 'zh-CN');
    },
    set(loc) {
      if (this.storageList.indexOf('cookie') > -1) {
        cookie.set('locale', loc, {
          expires: this.cookieMaxAge
        });
      }
    },
    i18n() {
      const langs = i18n[this.get()] || i18n['zh-CN'];
      langs.get = (_keys) => {
        if (!isString(_keys)) {
          return `${_keys}`;
        }
        const keys = _keys.split('.');
        const keyName = keys[keys.length - 1];
        let ret = langs;
        if (keys.length > 1) {
          keys.forEach((name) => {
            if (!ret[name]) {
              ret = {};
              return ret;
            }
            ret = ret[name];
            return ret;
          });
        }
        return ret[keyName] ? ret[keyName] : keyName;
      };
      return langs;
    }
  };
  return Object.assign(defaultOptions, options);
};
// 让页面模块能够使用this.i18n
export const locale = Locale();

export default function LocalPlugin() {
  /* eslint-disable */
  return function () {
    this.$locale = locale;

    const lang = this.$locale.i18n();

    if (!this.$t) {
      this.$t = (key, data) => {
        const content = lang[key];
        if (content === undefined) {
          return '';
        }
        if (content && data && typeof data === 'object') {
          return content.replace(/(.?)\${\s*(.*?)\s*(\\|)}/g, (input, g1, g2, g3) => {
            if (g1 === '\\' || g3 === '\\') {
              // 反斜杠转义,不做处理
              return g1 === '\\' ? input.substr(1) : input;
            }
            if (!g2) {
              // 没有变量名
              return g1;
            }
            let str = data[g2];
            if (str === undefined) {
              str = '';
            }
            return `${g1}${str}`;
          });
        }
        return content;
      };
    }

    this.i18n = lang;
    this.setState({
      i18n: lang
    });

    window.lang = this.$locale.i18n();
  }
}

这里需要说明的是this.$t的作用,this.i18n只能取静态文本,this.$t是一个函数,能够支持静态文本,动态文本的替换。

本文链接:www.my-fe.pub/post/web-project-code-share-1.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。