HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

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:371843

评论

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

软件开发

Geek_8da502

iZotope RX 10 for Mac v10.4.2激活版:创新的音频分析功能

iMac小白

浅谈政企风险防控体系

鲸品堂

风控 风控系统

使用 Amazon Bedrock 完成你的问答需求

亚马逊云科技 (Amazon Web Services)

人工智能 亚马逊云科技 大语言模型

史上最全前端知识点+高频面试题合集,十二大专题,命中率高达95%

程序员改bug

程序员 架构 前端 大前端 web前端

【大语言模型基础】60行Numpy教你实现GPT-原理与代码详解

EquatorCoco

Numpy 语言模型 GPT

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 国赛选手带你看 Elasticache Serverless

亚马逊云科技 (Amazon Web Services)

云计算 Serverless re:Invent Amazon ElastiCache

分页合理化是什么?

不在线第一只蜗牛

分页操作

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 王炸产品 Amazon Q,你的 AI 助手

亚马逊云科技 (Amazon Web Services)

re:Invent 生成式人工智能 Amazon CodeWhisperer Amazon Q

2024年度计划新视角:5种情况下你或你的公司可能不需要SEO

九凌网络

百度点石行业实践成果获选2023信通院大数据“星河”优秀案例

百度安全

淘宝/天猫商品API:实时数据获取与安全隐私保护的指南

Noah

Python 中 key 参数的含义及用法

快乐非自愿限量之名

Python 编程语言 开发语言

小语种才是独立站新风口,英文建站不再是唯一选择!

九凌网络

在FinClip中怎么使用小程序插件?

Geek_2305a8

铭文挖矿系统

区块链技术

解锁加密生态:用户钱包画像分析

Footprint Analytics

区块链 加密货币 钱包画像

云智·智算大会|大模型安全解决方案持续升级

百度安全

通过Environment获取属性文件的值,竟然会调用到JNDI服务!!!| 京东云技术团队

京东科技开发者

面对勒索病毒,金融机构该怎么办

XSKY星辰天合

开发者都能玩转的大模型训练

亚马逊云科技 (Amazon Web Services)

机器学习 canvas re:Invent AIGC Amazon SageMaker

考研二战失败,自学前端2个月,找到了9K的工作

程序员改bug

编程 性能优化 前端 大前端 前端程序员

关于数据可视化分析、过程及工具

2D3D前端可视化开发

数据可视化 数据可视化工具 数据可视化设计 数据可视化过程 数据可视化步骤

有什么好用的C/C++源代码混淆工具?

Geek_66e2f3

"前端面试笔记"在互联网上火了,完整版开放下载

程序员改bug

架构 性能优化 前端 大前端 工程化

再获权威奖项!百度安全DDoS防护服务斩获云安全联盟CSA 2023安全金盾奖

百度安全

SmartSVN for Mac v14.4激活版:直观的用户界面与高效的性能

iMac小白

Stata 15 for Mac v15.1永久激活版:直观的用户界面与高效的命令语法

iMac小白

苹果证书p12和描述文件的创建方法

"云原生:构建未来应用的革命性方法"

啊川..

2023

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