另外也可以换成基于 seajs 3.x 的方案
背景
前端项目较多, 有跨项目共用组件的需求
- 希望有一套可以在各个项目中使用的公共组件(模块), 统一实现避免重复造轮子
- 公共组件统一维护
统一引用
于是我们可以选择将公共组件从各个项目中抽离出来, 形成一个公共组件库的项目来服务各个业务项目. 为了便于各个项目使用, 我们会将公共组件发布到 CDN, 这样各个项目就只需要按照如下方式引用即可.
<!-- 项目需要哪个公共组件就引用哪个公共组件 -->
<link rel="stylesheet" href="//cdn.com/component/component1/component1.css">
<link rel="stylesheet" href="//cdn.com/component/component2/component2.css">
<script src="//cdn.com/component/component1/component1.js"></script>
<script src="//cdn.com/component/component2/component2.js"></script>
如你所知, 这种方式的组件方案, 在前端的圈子里由来已久, 特别是 jQuery
的生态圈, 比比皆是. 例如我们熟知的 Bootstrap
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>
在过去很长的一段时间内, 前端都是在这种原始的方式中摸索着, 至今还有很多项目是这样做的.
这种方式最大的弊端就是需要人为地来处理依赖关系, 包括组件本身的依赖, 以及组件之间的依赖关系. 当组件较多时, 就很容易出现因为组件引用不当而产生的问题. 例如:
-
组件怎么没有样式? 某个组件是有 CSS 文件的, 但引用的时候忘记了
<!-- 忘记引用 component1 的 css 文件了 --> <script src="//cdn.com/component/component1/component1.js"></script>
-
组件怎么跑不起来? 某个组件依赖了另外的组件, 但没有被引用进来
<!-- 忘记引用 component1 依赖的 component-common 了 --> <script src="//cdn.com/component/component1/component1.js"></script>
-
组件还是跑不起来? 依赖的组件是被引用进来的, 但引用的顺序不对
<!-- component-common 的引用顺序不对, 应该要放在 component1 上面, 先于 component1 执行 --> <script src="//cdn.com/component/component1/component1.js"></script> <script src="//cdn.com/component/component-common/component-common.js"></script>
模块化自动处理依赖
使用组件的人需要自己处理组件的依赖关系, 这种方式是逆天的.
是不是可以用一种简单的模块化手段来管理项目中的前端资源呢?
于是我们可以选择一种统一的前端模块化方案(例如 AMD/CMD), 统一的定义模块使用模块, 自动加载依赖.
这样我们就达成了
- 统一的前端模块化方案, 促成统一的写代码的风格, 并避免了全局变量和冲突的问题
- 统一的模块调用方式, 使用起来更简单, 使用者无需考虑依赖的问题
确定方案
- 使用 RequireJS 的 AMD 模块化方案
- 使用 RequireJS 的 CSS 插件, 以便将组件的样式也作为一种依赖
- 将共用模块(组件)发布到 CDN
- 将共用模块(组件)统一配置起来, 配置文件发布到 CDN
- 项目引用 CDN 上的共用模块配置文件, 通过模块名来使用共用模块(组件), 即达到跨项目共用组件的目标
- 遵循模块规范
- 不使用前端构建工具来打包模块, 模块建议以 SRP 的原则开发和使用
适用项目(场景)
比较适合的项目: 项目没有前后端分离, 需要前后端人员一起开发的企业级后台项目(一般是 PC 端), 例如内部的管理系统
或者是这种项目
- 没有前后端分离
- 对前端代码有模块化的需求, 但希望尽可能简单, 这样前后端人员的接受程度都会比较高
- 可以容忍前端的一些性能指标, 特别是模块较多时的请求数量, 所以可能更适合 PC 端
- 为了减少复杂度, 不加入构建流程来打包模块, 这样也可以更方便地调试线上的代码
实现方案
将模块分为两种
- 本地模块, 即项目的内部模块
- 远程模块, 即共用的外部模块
模块化规范
-
使用 Simplified CommonJS Wrapper 方式来定义模块
-
将所有
require
语句都放在模块的最前面, 建议采用以下顺序- 样式
- 共用组件模块
- 项目公共模块
- 页面模块
-
但一定要注意, 这只是为了提升写代码的体验, 底层的原理还是遵循 AMD 并行加载依赖的方式
-
例如
define(function(require, exports, module) { // 采用了这样的写法来加载依赖, 并不意味着 a 模块一定先于 b 模块执行 var a = require('./a.js'); var b = require('./b.js'); }); // 其原理上还是会转换成 AMD 本身的模块加载方式 define(['./a.js', './b.js'], function(a, b) {});
-
-
配置
baseUrl
以及css
和text
这两个插件例如
require.config({ baseUrl: './', map: { '*': { 'css': '//cdn.bootcss.com/require-css/0.1.10/css.min.js', 'text': '//cdn.bootcss.com/require-text/2.0.12/text.min.js' } } }); // 这样所有项目就都可以使用插件了 require('css!./index.css'); var tpl = require('text!./index-tpl.html');
-
使用模块时带上文件的后缀名
- 避免
paths
配置与模块的(目录)路径冲突
- 避免
-
使用相对路径来引用本地模块
-
避免
map
配置与模块的(目录)路径冲突 -
例如
var mod = require('./mod.js');
-
-
使用模块名来引用远程模块, 是通过
map
配置来统一维护映射的模块名与模块 URL-
例如
// 配置公共模块 map: { '*': { // 注意 JS 模块映射的模块ID 和 URL 都要带上 .js 后缀 'component1.js': '//cdn.com/component/component1/1.0.0/component1.js', 'component2.js': '//cdn.com/component/component2/1.1.0/component2.js', 'component3.js': '//cdn.com/component/component3/1.2.0/component3.js', // 注意 CSS 模块的配置有点特殊: 模块ID 和 URL 都不要添加 .css 后缀名 'style': '//cdn.com/component/style/1.0.0/style' // ... } } // 使用公共模块 require('css!style.css'); var component1 = require('component1.js');
-
避免在公共模块中使用
text
插件, 因为text
插件是以XHR
来加载内容的, 此时会有跨域问题, 除非你配置好了CORS
-
-
如果项目的静态资源最终要上传到 CDN, 那么也需要避免使用
text
插件, 原因就是会有跨域问题例如
// 引用 CDN 上的模块配置文件 require(['//cdn.com/component/require-base-config.js'], function() { // 开发环境 // require(['./index/index.js']); // 正式环境 require(['//cdn.com/project1/index/index.js']); });
项目示例
使用方法
将共用组件项目示例和使用共用组件的项目示例分别下载下来, 作为项目的模版来使用即可