【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

QQ 音乐商业化 Web 团队:前端工程化实践总结(一)

  • 2019-10-31
  • 本文字数:6014 字

    阅读完需:约 20 分钟

QQ音乐商业化Web团队:前端工程化实践总结(一)

本文主要介绍在前端工程化的一些探索和实践,结合移动端的基础库重构和 UI 组件库开发这两个项目详细介绍工程化方案 。

随着业务的不断扩展,团队的项目越来越多,面对日益复杂的业务场景和代码逻辑,我们发现在前端工程化方面团队还有很多需要优化的地方。现有的解决方案已经无法满足各种复杂的场景,我们每天都在疲于应付很多重复的工作,为此我们基于移动端基础库重构和 UI 组件库的建设这两个项目对团队的项目构建流程进行了详细的分析和梳理,并制定了一套适用于团队的工程化方案。

浅谈前端工程化

前端工程化是一个非常广泛的议题,包含的技术和解决方案也是非常丰富的。一个前端工程的生命周期可以大致划分为这四个过程:



前端工程的生命周期


任何在这四个过程中应用的系统化、严格约束、可量化的方法都可以称之为工程化。工程化的程度越高,在工作中因人的个体差异性导致的缺陷或者短板就会越少,项目质量可以得到更有效的保障。对上面四个过程的工程化并不是完全分隔的,而是相辅相成,比如开发阶段的优化也会对测试、部署和维护产生很大的影响。


下面从模块化、组件化、规范化和自动化这四个方面进行具体介绍。

模块化

模块化可以对复杂逻辑进行有效分割,每个模块更关注自身的功能,模块内部的数据和实现是私有的,通过向外部暴露一些接口来实现各模块间的通信。开发阶段前端需要关注 JS、CSS 和 HTML,下面我们将分别对 JS、CSS、HTML 的模块化进行简单介绍。

1. JS 模块化

JS 模块化是一个逐渐演变的过程,开始的 namespace 概念实现了简单对象封装,约定私有属性使用_开头,到后来的 IIFE 模式,利用匿名函数闭包的原理解决模块的隔离与引用,下面介绍现在比较流行的几种模块化标准。

2. CommonJS

Nodejs 中的模块化方案,就是基于 CommonJS 规范实现的。一个文件就是一个模块,有自己的作用域,没有 export 的变量和方法都是私有的,不会污染全局作用域,模块的加载是运行时同步加载的。CommonJS 可以细分为 CommonJS1 和 CommonJS2,二者的模块导出方式不同,CommonJS2 兼容 CommonJS1,增加了 module.exports 的导出方式,现在一般所指的都是 CommonJS2。


  • 每个文件一个模块,有自己的作用域,不会污染全局;

  • 使用 require 同步加载依赖的其他模块,通过 module.exports 导出需要暴露的接口;

  • 多次 require 的同一模块只会在第一次加载时运行,并将运行结果缓存,后续直接读取缓存结果,如果需要重新执行,需要先清理缓存;

  • Nodejs 环境下可以直接运行,各个模块按引入顺序依次执行。


module.exports.add = function (a, b) {    return a + b;}
exports.add = function (a, b) { return a + b;}
const sum = require('sum');sum.add(1, 2);
复制代码


AMD


浏览器加载 js 文件需要进行网络请求,而网络请求的耗时是不可预期的,这使得 CommonJS 同步加载模块的机制在浏览器端并不适用,我们不能因为要加载某个模块 js 而一直阻塞浏览器继续执行下面的代码。AMD 规范则采用异步的方式加载模块,允许指定回调函数,这非常适合用于浏览器端的模块化场景。


  • 使用 define 定义一个模块,使用 require 加载模块;

  • 异步加载,可以并行请求依赖模块;

  • 原生 JavaScript 运行环境无法直接执行 AMD 规范的模块代码,需要引入第三方库支持,如 requirejs 等;


// 定义一个模块define(id ? , dependencies ? , factory); // 引用一个模块require([module], callback)
复制代码


CMD


类似于 AMD 规范,是应用在浏览器端的 JS 模块化方案,由 sea.js 提出,详见 https://www.zhihu.com/question/20351507


UMD


UMD 规范兼容 AMD 和 CommonJS,在浏览器和 Nodejs 中均可以运行。


(function (root, factory) {    if (typeof define === 'function' && define.amd) {                define(['jquery', 'underscore'], factory);    } else if (typeof exports === 'object') {                module.exports = factory(require('jquery'), require('underscore'));    } else {                root.returnExports = factory(root.jQuery, root._);    }}(this, function ($, _) {        function a() {};    function b() {};    function c() {};
return { b: b, c: c }}));
复制代码


ES6 Module


ES6 从语言标准的层面上实现了模块化,是 ECMA 提出的模块化标准,后续浏览器和 Nodejs 都宣布会原生支持,越来越受开发者青睐。


  • 使用 import 引入模块,export 导出模块;

  • 与 CommonJS 的执行时机不同,只是个只读引用,只会在真正调用的地方开始执行,而不是像 CommonJS 那样,在 require 的时候就会执行代码;

  • 支持度暂不完善,需要进行代码转换成上面介绍的某一种模块化规范。


在浏览器中可以通过下面的方式引入 es6 规范的模块 js:


<script type="module" src="foo.mjs"></script>
<script type="module" src="foo.mjs" defer></script>
复制代码


defer 和 async 不同,它会阻塞 DomContentLoaded 事件,每个模块 js 会根据引入的顺序依次执行。


随着更多浏览器对 ES6 的支持,现在有一些方案开始提出直接使用 ES2015+的代码在浏览器中直接执行来提高运行效果,这篇文章《Deploying ES2015+ Code in Production Today》中有详细的介绍,可以结合这份性能测试报告综合评估 ES6 在 node 以及各种浏览器环境下的执行效率对比。

3. CSS 模块化

CSS 自诞生以来,基本语法和核心机制一直没有本质上的变化,它的发展几乎全是表现力层面上的提升。不同于 JS,CSS 本身不具有高级编程属性,无法使用变量、运算、函数等,无法管理依赖,全局作用域使得在编写 CSS 样式的时候需要更多人工去处理优先级的问题,样式名还有压缩极限的问题,为此,出现了很多“编译工具”和“开发方案”为 CSS 赋予“编程能力”。


预处理器


随着页面越来越复杂,为了便于开发和维护,我们常常会将 CSS 文件进行切分,然后再将需要的文件进行合并。诸如 LESS、SASS、Stylus 等预处理器为 CSS 带来了编程能力,我们可以使用变量、运算、函数,@import 指令可以轻松合并文件。但各种预处理器并不能完全解决全局作用域的问题,需要结合 namespace 的思想去命名。


OOCSS & SMACSS


OOCSS 和 SMACSS 都是有关 css 的方法论。OOCSS(Object Oriented CSS)即面向对象的 CSS,旨在编写高可复用、低耦合和高扩展的 CSS 代码,有两个主要原则,它们都是用来规定应该把什么属性定义在什么样式类中。


  • Separate structure and skin(分离结构和主题)

  • Separate container and content(分离容器和内容)


SMACSS(Scalable and Modular Architecture for CSS)是可扩展模块化的 CSS,它的核心就是结构化 CSS 代码,则有三个主要规则:


  • Categorizing CSS Rules (CSS 分类规则):将 CSS 分成 Base、Layout、Module、State、Theme 这 5 类。

  • Naming Rules(命名规则):考虑用命名体现样式对应的类别,如 layout-这样的前缀。

  • Minimizing the Depth of Applicability(最小化适配深度):降低对特定 html 结构的依赖。


/* 依赖html结构,不提倡 */.sidebar ul h3 { }
/* 建议直接定义 */.sub-title { }
复制代码


BEM


BEM 是一种 CSS 命名规范,旨在解决样式名的全局冲突问题。BEM 是块(block)、元素(element)、修饰符(modifier)的简写,我们常用这三个实体开发组件。


  • 块(block):一种布局或者设计上的抽象,每一个块拥有一个命名空间(前缀)。

  • 元素(element):是.block 的后代,和块一起形成一个完整的实体。

  • 修饰符(modifier):代表一个块的状态,表示它持有的一个特定属性。


在选择器中,BEM 要求只使用类名,不允许使用 id,由以下三种符号来表示扩展的关系:


  • 中划线( - ) :仅作为连字符使用,表示某个块或者某个子元素的多单词之间的连接记号。

  • 双下划线( __ ):双下划线用来连接块和块的子元素。

  • 单下划线( _ ):单下划线用来描述一个块或者块的子元素的一种状态。


.type-block__element_modifier {}
复制代码


从上面 BEM 的命名要求可以看到,类名都很长,这就导致在对 CSS 文件进行压缩的时候,我们无法得到更大的优化空间。而且 BEM 仅仅是一种规范,需要团队中的开发者自行遵守,在可靠性上无法得到有效保障,而且还可能和第三方库的命名冲突。


CSS in JS


CSS in JS 是一种比较激进的方案,彻底抛弃了 CSS,完全使用 JS 来编写 CSS,又用起了行内样式(inline style),它的发展得益于 React 的出现,具体的原因可以参见组件化这部分内容。


  • 解决全局命名污染的问题;

  • 更贴近 Web 组件化的思想;

  • 可以在一些无法解析 CSS 的运行环境下执行,比如 React Native 等;

  • JS 赋予 CSS 更多的编程能力,实现了 CSS 和 JS 间的变量共享;

  • 支持 CSS 单元测试,提高 CSS 的安全性;

  • 原生 JS 编写 CSS 无法支持到很多特性,比如伪类、media query 等,需要引入额外的第三方库来支持,各种库的对比详见 css-in-js;

  • 有运行时损耗,性能比直接 class 要差一些;

  • 不容易 debug;


下面以 styled-components 为例:


import React from 'react';import styled from 'styled-components';
const Container = styled.div` text-align: center;`;

const App = () => ( <Container> It is a test! </Container>);
render(<App />, document.getElementById('content'));
复制代码


构建后的结果如下,我们发现不会再有.css 文件,一个.js 文件包含了组件相关的全部代码:


var _templateObject = _taggedTemplateLiteral(['\n  text-align: center;\n'], ['\n  text-align: center;\n']);
function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } }));}
var Container = _styledComponents2.default.div(_templateObject);
var App = function App() { return _react2.default.createElement( Container, null, 'It is a test!' );};
复制代码


CSS module


CSS module 则最大化地结合了现有 CSS 生态和 JS 模块化的能力,以前用于 CSS 的技术都可以继续使用。CSS module 最终会构建出两个文件:一个.css 文件和一个.js。


  • 解决全局命名污染的问题;

  • 默认是局部的,可以用:global 声明全局样式;

  • 受 CSS 的限制,只能一层嵌套,和 JS 无法共享变量;

  • 能支持现在所有的 CSS 技术。


以 webpack 为例,使用 css-loader 就可以实现 CSS module:


module.exports = {    ...    module: {        rules: [            ...            {                loader: 'css-loader',                options: {                    importLoaders: 1,                    modules: {                        localIdentName: "[name]__[local]--[hash:base64:5]"                    },
} } ... ] } ...}
复制代码


下面是一个组件开发的例子:


/* style.css */.color {    color: green;}
:local .className .subClass :global(.global-class-name) { color: blue;}/* component.js */import styles from './style.css';elem.outerHTML = `<h1 class=${styles.color}>It is a test title</h1>`;
复制代码


构建运行后生成的 dom 结构如下:


<h1 class="style__color--rUMvq">It is a test title</h1>
复制代码


component.js 中 styles 变量的值如下,我们看到声明成:global 的类名.global-class-name 没有被转换,具有全局作用域。


const styles = {    "color": "style__color--rUMvq",    "className": "style__className--3n_7c",    "subClass": "style__subClass--1lYnt"}
复制代码


说明:React 对样式如何定义并没有明确态度,无论是 BEM 规范,还是 CSS in JS 或者 CSS module 都是支持的,选择何种方案是开发者自行决定的。

组件化

最初,网页开发一般都会遵循一个原则”关注点分离”,各个技术只负责自己的领域,不能混合在一起,形成耦合。HTML 只负责结构,CSS 负责样式,JS 负责逻辑和交互,三者完全隔离,不提倡写行内样式(inline style)和行内脚本(inline script)。React 的出现打破了这种原则,它的考虑维度变成了一个组件,要求把组件相关的 HTML、CSS 和 JS 写在一起,这种思想可以很好地解决隔离的问题,每个组件相关的代码都在一起,便于维护和管理。


我们回想一下原有引用组件的步骤:


1.引入这个组件的 JS;


2.引入这个组件的样式 CSS(如果有);


3.在页面中引入这个组件的;


4.最后是编写初始化组件的代码。


这种引入方式很繁琐,一个组件的代码分布在多个文件里面,而且作用域暴露在全局,缺乏内聚性容易产生冲突。


组件化就是将页面进行模块拆分,将某一部分独立出来,多个组件可以自由组合形成一个更复杂的组件。组件将数据、视图和逻辑封装起来,仅仅暴露出需要的接口和属性,第三方可以完全黑盒调用,不需要去关注组件内部的实现,很大程度上降低了系统各个功能的耦合性,并且提高了功能内部的聚合性。

1.React、Vue、Angular…

React、Vue、Angular 等框架的流行推动了 Web 组件化的进程。它们都是数据驱动型,不同于 DOM 操作是碎片的命令式,它允许将两个组件通过声明式编程建立内在联系。


<!-- 数据驱动的声明式Declarative--><pagination     current={current} total={maxCount/20}     on-nav={this.nav(1)}></pagination>
<!-- DOM操作的命令式Imprective --><pagination id='pagination'></pagination><script>// 获取元素var pagination = document.querySelector('#pagination');
// 绑定事件pagination.addEventListener('pagination-nav', function(event){ ...})
// 设置属性$.ajax('/blogs').then(function( json ){ pagination.setAttribute('current', 0) pagination.setAttribute('total', json.length / 20)})</script>
复制代码


从上面的例子可以看到,声明式编程让组件更简单了,我们不需要去记住各种 DOM 相关的 API,这些全部交给框架来实现,开发者仅仅需要声明每个组件“想要画成什么样子”。


  • JSX vs 模板 DSL


React 使用 JSX,非常灵活,与 JS 的作用域一致。Vue、Angular 采用模板 DSL,可编程性受到限制,作用域和 JS 是隔离的,但也是这个缺点使得我们可以在构建期间对模板做更多的事情,比如静态分析、更好地代码检查、性能优化等等。二者都没有浏览器原生支持,需要经过 Transform 才能运行。

2.Web Component

Web Component 是 W3C 专门为组件化创建的标准,一些 Shadow DOM 等特性将彻底的、从浏览器的层面解决掉一些作用域的问题,而且写法一致,它有几个概念:


  • Custom Element: 带有特定行为且用户自命名的 HTML 元素,扩展 HTML 语义;


<x-foo>Custom Element</x-foo>
复制代码


/* 定义新元素 */var XFooProto = Object.create(HTMLElement.prototype);
// 生命周期相关XFooProto.readyCallback = function() { this.textContent = "I'm an x-foo!";};
// 设置 JS 方法XFooProto.foo = function() { alert('foo() called'); };
var XFoo = document.register('x-foo', { prototype: XFooProto });
// 创建元素var xFoo = document.createElement('x-foo');
复制代码


2019-10-31 16:371743

评论

发布
暂无评论
发现更多内容

【架构实战营作业】模块一:微信业务架构图&学生管理系统

聆息

Go语言,深入了解 select 实现原理

微客鸟窝

Go 语言 8月日更

微信业务架构和学生管理系统架构设计

王家辉

架构实战打怪升级day01

yphust

我的学生管理系统

白开水又一杯

#架构实战营

Go- 字符串、切片和数组的使用

HelloBug

数组 字符串 Go 语言 切片

吃透这份Github点赞120k的Spring全家桶笔记Offer拿到手软

Java~~~

Java spring 架构 面试 Spring Cloud

学生管理系统架构设计

晓波

架构实战营

阿里P8整理的《百亿级并发系统设计》实战教程,实在是太香了

Java~~~

Java 架构 面试 多线程 高并发

读书笔记 -《数据密集型应用系统设计》-索引

KayTin

架构实战营模块一作业-初识架构

娜酱

#架构实战营

游戏开发者的通关之旅,华为AGC for Games带来了什么?

脑极体

网络攻防学习笔记 Day117

穿过生命散发芬芳

网络安全 8月日更

美团年货-2020年(记录)

春秋易简

四面字节跳动Java研发岗,最终拿下Offer,只有努力,方能成功

Java~~~

Java 架构 面试 微服务 架构师

大专的我狂刷29天“阿里内部面试笔记”最终直接斩获十七个Offer

Java~~~

Java 架构 面试 JVM 多线程

Vue进阶(七十):了解 webpack 的 hash、chunkhash、contenthash

No Silver Bullet

Vue 8月日更

太厉害了,终于有人把TCP-IP协议整合成864页学习笔记了

Java~~~

Java 架构 面试 TCP 网络

架构实战之微信业务架构|学生管理系统

Nico

架构实战营

微信业务架构&毕设学生管理架构选择

Sky

架构实战营

微信业务架构图&毕业设计之学生管理系统(model1)

消失的子弹

微信 架构图

我的微信业务架构图

白开水又一杯

#架构实战营

微信业务架构图&学生管理系统设计思路

刘琦Logan

关于开发视图

编程三昧

8月日更 开发视图

你最想了解的红队实战攻防技术,来了

网络安全学海

黑客 网络安全 信息安全 WEB安全 安全漏洞

Vue进阶(七十一):webpack 插件实现自动抽取 css 主题色样式一键切换

No Silver Bullet

Vue 8月日更 主题色

Ubuntu Server 20.04 搭建安装Kubernetes

玏佾

Kubernetes k8s文档 k8s资源 #k8s

学习-1

4anonymous

让 KAS 给 GitOps 插上腾飞的翅膀

极狐GitLab

Kubernetes 极狐GitLabs

Cron调度任务入门

devpoint

crontab 8月日更

02. 到底什么是人工智能

数据与智能

人工智能

QQ音乐商业化Web团队:前端工程化实践总结(一)_行业深度_sara_InfoQ精选文章