Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端模块化的发展 #2

Open
jianghai opened this issue May 11, 2016 · 0 comments
Open

前端模块化的发展 #2

jianghai opened this issue May 11, 2016 · 0 comments

Comments

@jianghai
Copy link
Owner

jianghai commented May 11, 2016

前端模块化发展史

1 前言

前端非常特殊,特殊在由三种语言组成:HTML、CSS、JavaScript。

带来的问题就是资源类型多种多样,随着应用复杂度的提升,资源的类型和数量越来越多:

.html/.css/.js/.json/.svg/.jpg/.png/.less/.scss/.jsx ....

如何管理好这些资源,如何方便地复用,成为业界共同的问题,一路走来,实属不易。

2 HTML 直接管理

早期的前端极其简单,一段 HTML,少量 CSS 加上少量 JS

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="main.css">
  <script src="main.js"></script>
</head>
<body>
  <h1>Hello, world!</h1>
</body>
</html>

这个时期,应用非常简单,依赖的资源很少,所以不需要任何模块化管理方案,HTML 本身就能表示出整个模块的依赖关系。

但是随着应用复杂度的提升,依赖的资源越来越多:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/moduleA.css">
  <link rel="stylesheet" href="css/moduleB.css">
  <link rel="stylesheet" href="css/moduleC.css">
  <link rel="stylesheet" href="css/moduleD.css">
  <!-- D 依赖 A、B、C -->
  <script src="js/moduleA.js"></script>
  <script src="js/moduleB.js"></script>
  <script src="js/moduleC.js"></script>
  <script src="js/moduleD.js"></script>
  <script src="js/main.js"></script>
</head>
<body>
  <div class="moduleD">
    moduleD html content
  </div>
  <div class="moduleD">
    moduleD html content
  </div>
</body>
</html>

这种情况下暴露出很多问题:

  1. 全局变量过多,容易出现冲突
  2. 手动加载依赖,管理依赖顺序
  3. 关联的资源模块被拆分,不方便统一管理与复用

3 AMD 规范

此时,ServerJS 社区(CommonJS 的前称,发展史下阶段介绍)推出的 Modules/1.0 规范在 Node.js 等环境下比较成功

var module = require('./module')
// do sth... 

于是想在浏览器环境下推广,便将社区改名为 CommonJS,商讨下一版规范,但社区内部分歧较大,产生了三种流派:

  1. Modules/1.x 流派:认为已有规范足够,在浏览器运行前通过转换工具转换即可
  2. Modules/Async 流派:认为浏览器自身的特性应该用异步的方式动态加载,典型代表是 AMD 规范
  3. Modules/2.0 流派:介于二者之间

AMD 规范一度非常流行,很大程度上解决了模块依赖的问题,典型的实现者 RequireJS 使用方式如下

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/moduleA.css">
  <link rel="stylesheet" href="css/moduleB.css">
  <link rel="stylesheet" href="css/moduleC.css">
  <link rel="stylesheet" href="css/moduleD.css">
</head>
<body>
  <div class="moduleD">
    moduleD html content
  </div>
  <div class="moduleD">
    moduleD html content
  </div>
  <script src="js/main.js"></script>
</body>
</html>

moduleD.js

define(['moduleA', 'moduleB', 'moduleC'], function(A, B, C) {
  // moduleD logic
  return moduleD
})

main.js

require(['moduleD'], function(D) {
  // do sth...
})

相比第一阶段的优势:

  1. 通过回调函数传递模块,不再产生全局变量
  2. moduleD只需要定义一次,多个页面多次使用时就不需要再加载它的依赖模块了

但 RequireJS 关注的主要是 JS 模块的管理,关联的其他类型的资源模块仍需手动管理(其实这个不属于 AMD 规范的问题,而是 RequireJS 没有去实现其他类型资源的依赖管理)。

4 CommonJS 规范

随着 NodeJS 的兴起,写 JS 却用两套规范非常不方便,规范不一致,接口不一致,不利于模块共享。所以干脆在浏览器端也用 CommonJS 规范好了。

浏览器端不支持同步加载 JS,那么就在运行前打包好不就可以了,早期实现者 browserify 使用方式如下:

moduleD.js

var moduleA = require('./moduleA.js')
var moduleB = require('./moduleB.js')
var moduleC = require('./moduleC.js')

// moduleD logic

module.exports = moduleD

main.js

var moduleD = require('./moduleD.js')
// do sth...
$ browserify main.js -o bundle.js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/moduleA.css">
  <link rel="stylesheet" href="css/moduleB.css">
  <link rel="stylesheet" href="css/moduleC.css">
  <link rel="stylesheet" href="css/moduleD.css">
</head>
<body>
  <div class="moduleD">
    moduleD html content
  </div>
  <div class="moduleD">
    moduleD html content
  </div>
  <script src="bundle.js"></script>
</body>
</html>

5 Web Components

browserify 实现的仍然是 JS 模块管理,如何管理 CSS、图片、HTML 片段等资源呢?webpack 登场

webpack 可以管理所有类型资源的依赖,开发者再也不需要单独加载 JS 以外的依赖了

moduleD.js

var moduleA = require('./moduleA')
var moduleB = require('./moduleB')
var moduleC = require('./moduleC')
require('./moduleD.css')
require('./moduleD.tpl')

// moduleD logic

module.exports = moduleD

main.js

var moduleD = require('./moduleD')
// do sth...
$ webpack main.js bundle.js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div class="moduleD"></div>
  <div class="moduleD"></div>
  <script src="bundle.js"></script>
</body>
</html>

此时,moduleD 已经上升到组件的概念了,与外部唯一的接口就是DOM容器了。

但是调用组件、组件嵌套组件仍然需要手动与 DOM 树挂钩。

场景一,调用组件 ModuleD:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div class="moduleD"></div>
  <div class="moduleD"></div>
  <script src="bundle.js"></script>
</body>
</html>
var ModuleD = require('./moduleD')
var containers = document.querySelectorAll('.moduleD')

new ModuleD({
  el: containers[0]
})

new ModuleD({
  el: containers[1]
})

场景二,ModuleD 嵌套 ModuleE:

moduleD.tpl

<div>
  <div>Hello, E</div>
  <div class="moduleE"></div>
</div>

moduleD.js

var ModuleE = require('./moduleE')

new ModuleE({
  el: moduleE-element
})

使用起来非常繁琐,有没有一个组件化的类库来管理组件呢?比如最终代码可以这样写:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <moduleD>
    <div>Hello, E</div>
    <moduleE></moduleE>
  </moduleD>
  <moduleD></moduleD>
  <script src="bundle.js"></script>
</body>
</html>

其实,Web 组件化的概念早就提出,一些类库如 Polymer、React、Vue 等也已经实现。以 React 为例:

moduleD.jsx

var React = require('react')
var ModuleA = require('./ModuleA')
var ModuleB = require('./ModuleB')
var ModuleC = require('./ModuleC')
require('./ModuleD.css')

var ModuleD = React.createClass({
  render() {
    return (
      <div>
        <ModuleA />
        <ModuleB />
        <ModuleC />
      </div>
    )
  }
})

module.exports = ModuleD

main.jsx

var React = require('react')
var ReactDOM = require('react-dom')
var ModuleD = require('./ModuleD')

var App = React.createClass({
  render() {
    return (
      <div>
        <ModuleD />
      </div>
    )
  }
})

ReactDOM.render(<App />, document.getElementById('app'))

为什么把 HTML 放在 JS 中?

HTML 严格来说不属于编程语言,无法描述程序逻辑,所以如果要实现必然需要一些程序上的预处理等操作,与其在 HTML 上面附属一堆需要额外学习的逻辑语法:

<thead>
  <tr>
    <th v-for="key in columns"
      @click="sortBy(key)"
      :class="{active: sortKey == key}">
      {{key | capitalize}}
      <span class="arrow"
        :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
      </span>
    </th>
  </tr>
</thead>

不如直接用纯粹的 JS 语法去描述。

<tr>
  {columns.map(function(item) {
    return <th>{item.name}</th>
  })}
</tr>

也看个人喜好,各个类库设计思路不同罢了。

最终入口:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

6 ES6 模块规范

2015年6月,ECMAScript 6 正式发布,终于,JavaScript 有了自己的模块化体系。

目前浏览器不支持,需要一些 transpiler 转换成已有的模块打包工具可识别的语法:

b.js

import a form './a'
import './b.css'

// do sth...

export default b

转换后

var a = require('./a')
require('./b.css')

// do sth...

module.exports = b

7 总结

技术的发展总是由需求推动的:

  1. 应用复杂度提升,依赖资源越来越多,手动管理不方便,AMD 规范出现
  2. NodeJS 兴起,前后端 JS 需要共享模块,浏览器端应用 CommonJS 规范
  3. 前端不仅仅是 JS,其他资源也需要在一起管理,webpack 等工具出现,此时模块化不仅仅是单纯的 JS 了,正式拉开组件化的序幕
  4. 组件化需要一种组件化的类库去管理组件,Polymer、React、Vue 等类库相辅相成
  5. 面对诸侯割据般的模块规范,统一的语言层面上的模块化规范出现 — ES6 Modules

前端模块不仅仅是 JS,HTML、CSS、图片等等一切资源都是模块,如何复用资源、如何以最小的成本使用,就是本文的目的。

8 参考

Modules/1.0
CommonJS
AMD

@jianghai jianghai changed the title 前端模块化发展史 前端模块化的发展 Apr 3, 2018
@jianghai jianghai added why and removed why labels Apr 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant