一个前端工程的进化史

只有紧随时代的步伐,才不会被时代抛弃。在本文中,笔者试图和大家一起探索前端工程的可持续发展方案。

在刚开始学习前端的时候,相信都听过前端“三剑客”——HTML、CSS、JS。于是,我们上手写Demo,会构建如下结构的代码:

  • HTML

    • index.html

    • ...

  • CSS

    • style.css

    • ...

  • JS

    • index.js

    • ...

如果不是以前端作为立身之本,能够搭建这样的一个前端项目,很多时候就止步了。此处可参见一些由bootstrap等搭建的后台管理系统,哈哈。

如果入行前端是在三、四年之前,第一个上线项目也许基本与上述结构相同了。

随着学习的深入,我们了解到CSS预处理,更方便的写CSS样式及减少兼容性样式的编写。以Sass举例,我们的项目结构随之丰富为:

  • HTML

    • index.html

    • ...

  • CSS

    • style.css

    • ...

  • JS

    • index.js

    • ...

  • SASS

    • style.scss

    • variable.scss

    • ...

其中 CSS 目录下代码,是由 SASS 目录下代码编译后生成。这时候,我们要么依赖三方工具,比如笔者就用过 Koala 这款工具,用来将Sass文件编译成CSS文件。当然,在这个阶段,前端的编译工具Grunt/Gulp等,逐渐完善,也成为编译Sass等选择。

其实在上述的这些阶段,这样的前端项目,或许只能称为文件管理代码,还不能称之为工程。

随着前端的发展,各种针对前端代码“打包”工具的出现及不断完善,以及新的语法ES6、ES7、TypeScript等,新的思想带来的新的开发框架如Angular、Vue、React等,使得前端开发的代码,已经不能直接在浏览器运行起来了,需要做一些对应的“转换”才行。通常我们称这个“转换”的过程叫做打包。

直至到这里,前端项目在开发过程中,才真正的有进入到“工程环节”。

前面说这么多,实际上只是铺垫。目的在于为对于不太了解前端的同学,做一些解释和说明。当然,接下来要说的东西,相对来讲,会比较深入一些,会涉及一些细节。笔者争取用简单的语言,来和大家共同探讨。针对非前端专业的同学,如果遇到感觉晦涩的部分,可以先跳过,只要保证整体思路上能理解即可;对于想要对细节部分也把握的同学,如果有疑惑和质疑,笔者期待大家的留言。

正文

笔者以58部落项目举例。其大的演进大致可以分为3个阶段:

  • MVP项目阶段

  • 58部落业务阶段

  • 大部落业务阶段

项目的主要技术栈包括:

  • React: 用于构建用户界面的 JavaScript 库

  • Redux: JavaScript 应用状态管理容器

  • ES7: 相对于2015年前的JavaScript更加新潮的JavaScript

  • ESLint: JavaScript 语法规则和代码风格的检查工具

  • Sass: CSS扩展语言

  • CSSModules: 模块化的CSS样式编写方式

上述技术栈,已经我们传统意识上的JavaScript代码(编写即可得、即可运行)不太一样了。比如React中的JSX语法,非原生JavaScript语法,需要做一定的“转换”,才能被浏览器等JavaScript运行环境所识别;ES7一些语法特性,浏览器支持得还不够;Sass写出的样式,并不能被解析等等。这就要求我们所编写的代码,必须经过一定的“转换”才能真正的在浏览器中运行起来。这里我们选用的工具是 Webpack。

在项目的3个演进阶段中,技术选型是基本没有变化。有关项目选型,非本文的讨论范畴的主要,因此暂且按下不表。

一、演进阶段1:MVP项目阶段

对于“MVP项目”,可以理解为一次尝试性的产品实现。产品是为了验证某个想法,还没有完整的目标,当然也就无从谈起规划。对于工程同学来说,最重要的是功能可以实现,产品得以验证。该阶段的工程,其主要的要求就是:越快越好。

这样导致业务开发的同学,在工程搭建方面:

  • 业务无长远规划,必然只能着眼于当前

  • 无足够时间,去进行一些必要的思考和规划

一切以满足当前需求为准。

工程中涵盖一个小型前端工程的基本内容:

  • 源代码: React组件、Sass代码等

  • Webpack打包配置文件

源代码在工程的 src 目录下,根据功能分为:

  • pages: 页面集合

    • pageA

    • pageB

    • ...

  • shared: 各页面可复用的组件、方法、样式等

    • components

    • utils

    • styles

    • ...

在Webpack配置上,将 React,React-Dom等一些三方库,以 externals 的方式,在页面中单独引入:

{
    externals:{
        React:'react',
        ReactDom:'react-dom'
    }
}

然后每个页面会再打包出一个JS文件出来。最终每个页面的JS文件基本上是这样:

 <script src="./js/sdk.js" />
 <script src="./js/react.js"/>
 <script src="./js/react-dom.js"/>
 <script src="./js/page.js"/>

二、演进阶段2:58部落业务阶段

我们思考演进阶段1可能存在的问题:

1. 在工程中,无法直接调试模板

因为用的不是完全的前后端分离,还是需要涉及到页面调试问题。当然用 React 这种开发方式,模板基本上不用特别调试。所以需要本地有一个页面用于承载开发需要,后端模板还需要单独维护。

2. 工程中仅将React及ReactDom单独提取,那其他三方组件呢,是不是需要每次都重新打包进 pageX.js

三方组件通常来说,变动不像业务代码一样频繁。如果能不每次打包,对开发时打包效率上线时对用户资源变动,合理利用前端静态资源的缓存是不更好一些。

3. 既然抽离了公用的组件、方法、样式等,如果一个公用组件的改动,是不意味着依赖这个组件的页面都需要更新呢?

4. 问题3中如果通用的组件变更等,导致各page.js都需要更新,那么页面如果有3个,4个,5个... 基于 RMS (Resource Manage System)的前端静态资源更新机制,对于这样的变动,我们需要将所有相关的资源都去手动同步一遍。是否有更省时省力的方式呢?

当产品功能得到验证,业务开始步入正轨,这个时候团队对产品的未来规划已有大致的方向,开发团队也确认持续投入开发维护工作。这个阶段就要求项目工程能够对需求的变动有预期。

为了解决问题1,项目工程基于webpack的devServer,实现了 mock-server。其主要作用,就是提供模板运行环境和mock数据。

因为webpack的devServer是基于express开发的,因此我们用类似 Java 的 velocity 模板引擎 velocityjs 帮助项目在调试模式下渲染页面。从而将后端模板接入进来。

针对问题2/3,我们优化调整webpack的打包配置:

  • 将React/React-Dom等核心库打包成 vendor/core.js 文件。

  • 将一些常用的三方库打包成vendor/thirds.js

  • 针对上述两个vendor文件,引入DLL优化打包速度

  • 对于公用的组件或方法等,打包进bundle.js文件

所以,对一个页面来说,引入的JS资源就变成如下形式:

 <script src="./js/sdk.js" />
 <script src="./js/vendor/core.js"/>
 <script src="./js/vendor/thirds.js"/>
 <script src="./js/bundle.js"/>
 <script src="./js/page.js"/>

好处在于:

  • 核心的库 ReactReact-Dom 以及一些三方库,都避免了频繁更新

  • 对于公共组件、方法的改动,通常只需要更新 bundle.js 即可

有效的减少不必要代码的更新和每次更新的文件数量。

解决问题2/3, 给我们带来一个新的问题:

  • 如果一个模块有更新,我们怎么知道依赖它的页面是否需要更新还是仅更新bundle即可呢?

结合这个问题,以及上述问题4(需要更新多个资源),我们开发了一个npm包(@w/rms-client)来解决这个问题。这个包主要提供两个功能:

  • 批量更新RMS系统上管理的资源

  • 并在更新时进行MD5对比,仅在资源有变更时更新

这样就很好的解决上述的两个问题。同时还带来另一个好处:资源仅在有变更时进行更新,减少静态资源的更新频率。从而更加有效的利用CDN缓存及Native对静态资源的缓存,减少用户访问页面时的流量消耗,使页面在更多时候能更快速的呈现在用户面前。

这一阶段的核心,是在业务快速迭代的过程中,想法设法减少静态资源的更新频率及公用代码的复用性,从而 更加 有效的利用CDN缓存及Native的缓存,减少用户访问页面时的流量消耗,使页面在更多时候能更快速的呈现在用户面前。

三、演进阶段3:大部落业务阶段

一方面由于业务变更,原本拆分的项目——热议,从产品需求上需要进行整合;另一方面,部落自身的业务发展需要,演进阶段2的项目架构已经不能很好的支持业务迭代了:

  • 页面快速增加,已接近30+,页面的增加导致bundle.js代码量膨胀;开发和打包效率上也变得较缓慢。

  • 某些页面功能相对独立,与其他页面关联性不强,强制耦合经常会带来更新上的问题。

    可能由于这些页面依赖的公用组件或方法,在需求中有变动,导致bundle.js更新,引发其他不想关的页面代码也需要更新。测试同学该不该走查这些页面呢?

  • 参与项目的人员增加,多个需求需要并行测试问题日益凸显。

解决这些问题,有时候我们不能去空想。不管是写代码,还是在构建项目,都是一个循序渐进,不断学习别人,再自己创造的过程。

我们项目是基于React开发的,不免会去学习React的一些源码知识。其实笔者对于源码知识的学习倒不是很多,更多的是看它怎么对代码文件怎么拆分和管理了。在React的工程中,React、React-Dom以及其他跟React相关的库,都放在了同一个工程里,每个库拆分成一个文件夹...在持续的跟进中,发现这是“lerna”的一种代码管理机制。

lerna是GitHub上面开源的一款js代码库管理软件, 用来对一系列相互耦合比较大、又相互独立的js git库进行管理。解决各个库之间修改混乱、难以跟踪的问题。lerna可以优化这种情形下的工作流。

lerna的这种特性,正好可以满足我们的诉求。

两个项目虽然是独立运行,但中间存在一些重叠和耦合的部分。我们没有完全采用lerna的,但学习了lerna对于代码的组织方式。于是,源代码目录就由 src/* 变成如下形式:

 - packages/
   - core
   - news
   - packageA。
   - ...

我们将热议项目以一个package的形式,融入到部落的当前工程中。

对于部落的原有页面,我们也做了一些拆分。

  • core: 部落的核心流程页面,如部落首页、帖子详情等

  • personal: 用户相关的页面,如用户主页、消息提醒等

  • news: 则是热议项目工程中相关的代码

  • 其他独立功能页面集如paper(小纸条)、search(搜索)等

每个package,从形式上来讲,可以是一个独立的工程。通过这种组合,又将独立的工程进行有机的整合。由此,项目更能满足业务需求。下图是演进完成之后工程的完整目录结构图:

├── CHANGELOG.md
├── README.md
├── assets
├── config
│ ├── assets
│ ├── const.js
│ └── proxy.js
├── dist
├── docs
├── jsconfig.json
├── mock-server
├── package-lock.json
├── package.json
├── packages
│ ├── core
│ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── components
│ │ │ │ ├── containers
│ │ │ │ ├── index.jsx
│ │ │ │ ├── ...
│ │ │ ├── ...
│ │ ├── shared
│ ├── personal
│ ├── news
│ ├── shared
│ ├── ...
├── scripts
└── webpack

对于并行测试,因为依赖工具(RMS)的限制,我们从Charles代理入手,借鉴后端测试指host或map-remote的方式。目前是将项目的静态资源上传到指定的FTP,通过文件代理的方式,将线上的静态资源进行代理测试,从而达到了并行测试的目的。

总结

回顾上述几个阶段,每个阶段都不完美。比如当前阶段下,我们对资源更新、对分支管理、对打包构建等,都没有很好的统一管理方式,这要求我们需要继续升级、更新。

不管是刀根火种的年代,还是在打包工具比较成熟的现代,我们不能说那时候的技术人技术更强,也不能说现代技术人的技术缺乏。作为技术人,首先我们得顺应时代的发展,跟紧时代的脉搏,才不至于落伍,才有机会发展创造。对我们的项目工程来说,也是一样的道理,紧跟产品需求而不断变化,才能适应新的产品需求,才能更好的支持业务发现。否则,我们就会成为现代社会中,进行耕种的农人,白白拥有先进的耕种机器,可是因为缺乏驾驶技术或没有动力来源等,导致播种效率无法跟生产工具相适应。要完成等量的工作,需要付出更多的时间。

附录

  1. webpack

    webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换(transform)、打包(bundle)或包裹(package)任何资源(resource or asset)。

  2. 项目的定义

    项目是一个组织为实现既定的目标,在一定的时间、人员和其他资源的约束条件下,所开展的一种有一定独特性、一次性的工作。项目是人类社会特有的一类经济、社会活动形式,是为创造特定的产品或服务而开展的一次性活动。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章