当前位置: 金莎娱乐场app下载 > 金莎娱乐app > 正文

都需要加载JS、CSS甚至大量第三方类库,模块化是

时间:2020-03-07 20:13来源:金莎娱乐app
物理层 :通过config.js来统一配置按需加载的打包策略,并在模板中对编译加戳后的物理文件进行引用。 安装 # 自动生成package.json文件$ npm init# 本地安装webpack,会将依赖写入package.json中

物理层:通过config.js来统一配置按需加载的打包策略,并在模板中对编译加戳后的物理文件进行引用。

安装

# 自动生成package.json文件
$ npm init

# 本地安装webpack,会将依赖写入package.json中。
$ npm install --save-dev webpack

# 不推荐安全安装webpack,它会锁定webpack到指定版本,在使用不同webpack版本的项目中可能会导致构建失败。

在项目使用npm首先会在本地模块中寻找webpack。

# package.json
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "test": "npm run unit",
    "lint": "eslint --ext .js,.vue src test/unit/specs",
    "build": "node build/build.js"
  },

本地安装webpack后,node_modules/.bin/webpack是它的二进制程序。

总体架构

为了实现一种自然、便捷、高性能、一体化的模块化方案,我们需要解决以下一些问题,

  • 模块静态资源管理,一般模块总会包含 JavaScript、CSS 等其他静态资源,需要记录与管理这些静态资源
  • 模块依赖关系处理,模块间存在各种依赖关系,在加载模块的时候需要处理好这些依赖关系
  • 模块加载,在模块初始化之前需要将模块的静态资源以及所依赖的模块加载并准备好
  • 模块沙箱(模块闭包),在 JavaScript 模块中我们需要自动对模块添加闭包用于解决作用域问题

** 使用编译工具来管理模块 **

我们可以通过编译工具(自动化工具) 对模块进行编译处理,包括对静态资源进行预处理(对 JavaScript 模块添加闭包、对 CSS 进行 LESS 预处理等)、记录每个静态资源的部署路径以及依赖关系并生成资源表(resource map)。我们可以通过编译工具来托管所有的静态资源,这样可以帮我们解决模块静态资源管理、模块依赖关系、模块沙箱问题。

** 使用静态资源加载框架来加载模块 **

那么如何解决模块加载问题,我们可以通过静态资源加载框架来解决,主要包含前端模块加载框架,用于 JavaScript 模块化支持,控制资源的异步加载。后端模块化框架,用于解决 JavaScript 同步加载、CSS 和模板等模块资源的加载,静态资源加载框架可以用于对页面进行持续的自适应的前端性能优化,自动对页面的不同情况投递不同的资源加载方案,帮助开发者管理静态资源,抹平本地开发到部署上线的性能沟壑。 编译工具和静态资源加载框架的流程图如下:

图片 1

打包配置混乱,散落在各个目录结构中,经常出现重复打包和漏打包的现象,严重的还造成线上问题。

概述

随着页面表现形式越来越丰富,用户体验与交互越来越被关注。前端开发大量涌现出H5、CSS3、ES6等技术方案。前端开发从简单的类库调用转而关注和强调框架的应用。无论是使用类库还是框架,都需要加载JS、CSS甚至大量第三方类库。如何管理和组织这些静态资源,并保证它们在浏览器中可以快速、灵活的加载与更新呢?这是目前web开发倡导的模块系统所需面对和解决的问题。

模块系统

模块系统需要解决的是如何定义模块以及如何处理模块间的依赖关系。

web开发初期,常用JS文件加载使用<script>标签完成,这种方式带来的问题是每个文件中声明的变量都会出现在全局作用域内,即window对象中。模块间的依赖关系完全由文件的加载顺序所决定,显然这种模块组织方式存在很多弊端。

  • 全局作用域下容易造成变量冲突
  • 文件只能按照<script>标签的书写顺序进行加载
  • 开发者需自己解决模块/代码库的依赖关系
  • 大型web项目中这样的加载方式导致文件冗长且难以管理

你曾经遇到过这些问题吗?

  • 载入有问题的依赖项
  • 意外引入生产环境中无用的CSS和JS,使项目膨胀。
  • 意外的多次载入某些库
  • 遇到作用域问题
  • 使用NPM或Bower模块的构建系统,令人发狂的配置后才能正确使用。
  • 优化asset时担心弄坏它
    ...

模块化

前端模块化标准规范

  • CommonJS
    CommonJS规范中模块使用require()同步加载所依赖的模块,通过设置exports对象或module.exports对象的属性的方式,对外提供支持的接口。
    CommonJS的优点是可重用服务端模块、NPM中有大量模块使用CommonJS规范来书写,其缺点是同步的加载方式并不适合在浏览器的环境,由于浏览器的资源往往是异步加载的,而且不能非堵塞的并行加载多个模块。
    CommonJS典型实现如node.js、browserify、modules-webmake、wreq等。
  • AMD(Asynchronous Module Definition)
    AMD规范中模块通过define(id, dependencies,function)方法来定义,同时需指定模块的依赖关系,而且这些依赖的模块会被当做形参传入function方法中。而应用通过使用require()方法来调用所定义的模块。
    AMD的优点在于适合在浏览器环境中异步加载模块,可以并行加载多个模块。其缺点是增加开发成本且不利于代码的编写与阅读,另外,不符合通用的模块化的思维方式,只是一种妥协的实现。
    AMD典型的实现如require.js 和 curl。
  • ES6模块
    ECMAScript2015(ES6)增加了很多JS语言层面的模块化体系定义,ES6模块的设计思想是尽量的静态化,使得编译时即可确定模块的依赖关系,以及输入和输出的变量。
    ES6模块的优点是很容易进行静态分析,而且面向未来的ECMAScript标准。缺点在于原生浏览器还没有实现该标准,而且目前支持的模块较少。
    ES6典型的实现如Babel。

期望的模块系统

  • 兼容多种模块系统风格
  • 模块按需加载且不影响页面初始化速度
  • 支持多种资源文件
  • 支持静态分析及第三方类库
  • 合理的测试解决方案

什么是前端构建工具呢?

  • 文件打包
    每个页面会根据前端资源JS、CSS、图片等发起多次HTTP请求,大量页面叠加在一起,将极大地降低页面性能,造成页面加载很慢。那么,能不能将前端资源文件合并为一个文件呢?

  • 文件压缩
    压缩文件可提供页面性能,例如删除注释、删除空格、缩短变量名等,以减少文件体积加快传输速度提高页面性能,另外实现代码混淆破快其可读性以保护作者知识产权。

  • 前端模块化
    从大量<script>到webpack广泛使用,实际上是前端模块化发展的过程,期间有两个主要模块化标准CommonJS和AMD。

  • 编译和转换

浏览器是无法理解开发代码中SASS、LESS、JSX模块文件等,通过webpack的转换生成一份浏览器能够理解的生产代码。

图片 2

前端构建工具

什么是webpack呢?

webpack是前端资源模块化管理和打包工具,可将松散的模块按依赖和规则打包成符合生产环境部署的前端资源。可将按需加载的模块进行代码分隔并进行异步加载。通过loader的装换,任何形式的资源都可视作模块,如CommonJS模块、AMD模块、ES6模块、CSS、图片、JSON、CoffeeScript、LESS等。

图片 3

WebPack

webpack和gulp本质上并非同一类型工具,但它们都能完成相同的任务。

  • webpack是一个模块化工具(a module bundle)
  • gulp是一个任务运行器(a task runner)

图片 4

webpack vs gulp

为什么需要webpack呢?

webpack通过让JS来处理依赖关系和加载顺序,而非通过开发者的大脑。webpack可纯粹的在服务端运行来构建渐进增强式的应用。

webpack试图通过提出一个大胆的想法来减轻开发者的负担:如果开发过程一部分可以自动处理依赖关系会怎样呢?若可以简单地写代码,让构建过程最终只根据需求管理自己会怎样呢?

webpack的方式是:若webpack了解依赖关系,它会捆绑在生产环境中实际需要的部分。

  • 将依赖树拆分保证按需加载
  • 保证初始加载的速度
  • 所有静态资源可被模块化
  • 整合构建第三方库和模块
  • 适合构建大型SPA单页应用程序

本质上,webpack是一个现代JS应用程序的静态模块打包器(module bundle)。当webpack处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。

非模块化资源

在实际开发过程中可能存在一些不适合做模块化的静态资源,那么我们依然可以通过声明依赖关系来托管给静态资源管理系统来统一管理和加载,

{require name="home:static/index/index.css" }

如果通过如上语法可以在页面声明对一个非模块化资源的依赖,在页面运行时可以自动加载相关资源。

而js代码本身又不是模块化形式的。就使得代码结构很混乱,各种调用方式都存在,开发人员在写代码的时候不知道该直接调用还是模块化调用。

概念

webpack是高度可配置的,先了解四个核心概念:

  • 入口(entry)
  • 输出(output)
  • 加载器(loader)
  • 插件(plugins)

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。

当然,模块化框架并非前端开发的银弹。我们在重构过程中也碰到了“不好调试”、“出现循环依赖后不好定位”等问题。这也是我们接下来要尝试解决的方向。

入口(entry)

入口起点(entry point)是webpack应该使用哪个模块,来作为构建内部依赖的开始。进入入口起点后,webpack会找出有哪些模块和库的入口起点。

每个依赖项随即被处理,最后输出到称之为bundles的文件中。可通过wepack配置中配置entry属性,来指定一个入口起点。

前端模块化带来的性能问题

很多主流的模块化解决方案通过 JavaScript 运行时来支持“匿名闭包”、“依赖分析”和“模块加载”等功能,例如“依赖分析”需要在 JavaScript 运行时通过正则匹配到模块的依赖关系,然后顺着依赖链(也就是顺着模块声明的依赖层层进入,直到没有依赖为止)把所有需要加载的模块按顺序一一加载完毕,当模块很多、依赖关系复杂的情况下会严重影响页面性能。

因为以上问题,我们每次产品升级都如履薄冰,需要非常小心谨慎,测试也很耗费精力。为了提高效率,我们必须要重构。

JavaScript 模块

上面我们介绍了一个模板模块是如何定义、调用以及处理依赖的,接下来我们来介绍一下模板模块所依赖的 JavaScript 模块是如何来处理模块交互的。我们可以将任何一段可复用的 JavaScript 代码放到一个 JS 文件中,这样就可以定义为一个 JavaScript 类型的模块,我们无须关心“ define ”闭包的问题,我们可以获得“ CommonJS ”一样的开发体验,下面是 nav.js 中的源码.

// common/widget/nav/nav.js
var $ = require('common:widget/jquery/jquery.js');

exports.init = function() {
    ...
};

我们可以通过 require、require.async 的方式在任何一个地方(包括 html、JavaScript 模块内部)来调用我们需要的 JavaScript 类型模块,require 提供的是一种类似于后端语言的同步调用方式,调用的时候默认所需要的模块都已经加载完成,解决方案会负责完成静态资源的加载。require.async 提供的是一种异步加载方式,主要用来满足“按需加载”的场景,在 require.async 被执行的时候才去加载所需要的模块,当模块加载回来会执行相应的回调函数,语法如下:

// 模块名: 文件所在 widget 中路径
require.async(["common:widget/menu/menu.js"], function( menu ) {
    menu.init();
});

一般 require 用于处理页面首屏所需要的模块,require.async 用于处理首屏外的按需模块。

如上面说到的,按照CommonJS规范,模块之间有require和use两种依赖形式,我们加入了第三种:fire。通过自定义事件的监听和触发,我们实现一种弱依赖的形式。

前端模块化并不等于 JavaScript 模块化

前端开发相对其他语言来说比较特殊,因为我们实现一个页面功能总是需要 JavaScript、CSS 和 Template 三种语言相互组织才行,如果一个功能仅仅只有 JavaScript 实现了模块化,CSS 和 Template 还是处于原始状态,那我们调用这个功能的时候并不能完全通过模块化的方式,那么这样的模块化方案并不是完整的,所以我们真正需要的是一种可以将 JavaScript、CSS 和 Template 同时都考虑进去的模块化方案,而非仅仅 JavaScript 模块化方案。

关于前端重构模块化的开发,我们按需加载为页面带来了很大的性能提升,但同时也为代码结构带来了很大的冲击,很多直接调用的方式被改为了模块化的调用形式(先判断模块是否存在,不存在就先加载对应的js,再执行回调)。

模板模块

我们可以将任何一段可复用的模板代码放到一个 smarty 文件中,这样就可以定义一个模板模块。在 widget 目录下的 smarty 模板(本文仅以 Smarty 模板为例)即为模板模块,例如 common 子系统的 widget/nav/ 目录

├── nav.css
├── nav.js
└── nav.tpl

下 nav.tpl 内容如下:

<nav id="nav" class="navigation" role="navigation">
    <ul>
        <%foreach $data as $doc%>
        <li class="active">
            <a href="#section-{$doc@index}">
                <i class="icon-{$doc.icon} icon-white"></i>{$doc.title}
            </a>
        </li>
        <%/foreach%>
    </ul>
</nav>

然后,我们只需要一行代码就可以调用这个包含 smarty、JS、CSS 资源的模板模块,

// 调用模块的路径为 子系统名称:模板在 widget 目录下的路劲
{widget name="common:widget/nav/nav.tpl" }

这个模板模块(nav)目录下有与模板同名的 JS、CSS 文件,在模板被执行渲染时这些资源会被自动加载。如上所示,定义 template 模块的时候,只需要将 template 所依赖的 JS 模块、CSS 模块存放在同一目录(默认 JavaScript 模块、CSS 模块与 Template 模块同名)下即可,调用者调用 Template 模块只需要写一行代码即可,不需要关注所调用的 template 模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。

QA人员可以根据每次提测的模块列表,评估出可能会影响到的case范围。

总结

本文是 fis 前端工程系列文章中的一部分,其实在前端开发工程管理领域还有很多细节值得探索和挖掘,提升前端团队生产力水平并不是一句空话,它需要我们能对前端开发及代码运行有更深刻的认识,对性能优化原则有更细致的分析与研究。fis 团队一直致力于从架构而非经验的角度实现性能优化原则,解决前端工程师开发、调试、部署中遇到的工程问题,提供组件化框架,提高代码复用率,提供开发工具集,提升工程师的开发效率。在前端工业化开发的所有环节均有可节省的人力成本,这些成本非常可观,相信现在很多大型互联网公司也都有了这样的共识。

本文只是将这个领域中很小的一部分知识的展开讨论,抛砖引玉,希望能为业界相关领域的工作者提供一些不一样的思路。欢迎关注fis项目,对本文有任何意见或建议都可以在fis开源项目中进行反馈和讨论。

作者:walter (http://weibo.com/u/1916384703) - F.I.S 

按照模块化进行重构之后,我们可以针对单个组件写case进行测试。这里用到了Qunit框架。效果如下图:

自适应的性能优化

现在,当我们想对模块进行打包,该如何处理呢,我们首先使用一个 pack 配置项(下面是 fis 的打包配置项),对网站的静态资源进行打包,配置文件大致为,

fis.config.merge({
    pack: {
        'pkg/aio.css': '**.css'
    }
});

我们编译项目看一下产出的 map.json(resource map),有何变化,

{
    "res": {
        "A/A.tpl": {
            "uri": "/template/A.tpl",
            "deps": ["A/A.css"]
        },
        "A/A.css": {
            "uri": "/static/csss/A_7defa41.css",
            "pkg": "p0"
        },
        "B/B.tpl": {
            "uri": "/template/B.tpl",
            "deps": ["B/B.css"]
        },
        "B/B.css": {
            "uri": "/static/csss/B_33c5143.css",
            "pkg": "p0"
        },
        "C/C.tpl": {
            "uri": "/template/C.tpl",
            "deps": ["C/C.css"]
        },
        "C/C.css": {
            "uri": "/static/csss/C_ba59c31.css",
            "pkg": "p0"
        },
    },
    "pkg": {
        "p0": {
            "uri": "/static/pkg/aio_0cb4a19.css",
            "has": ["A/A.css", "B/B.css", "C/C.css"]
        }
    }
}

大家注意到了么,表里多了一张 pkg 表,所有被打包的资源会有一个 pkg 属性 指向该表中的资源,而这个资源,正是我们配置的打包策略。这样静态资源管理系统在表中查找 id 为 A/A.css 的资源,我们发现该资源有 pkg 属性,表明它被备份在了一个打包文件中。

我们使用它的 pkg 属性值 p0 作为 key,在 pkg 表里读取信息,取的这个包的资源路径为 /static/pkg/aio0cb4a19.css_ 存入 uris 数组 中将 p0 包的 has 属性所声明的资源加入到 has 表,在要输出的 html 前面,我们读取 uris 数组 的数据,生成静态资源外链,我们得到最终的 html 结果:

<html>
    <link href="/static/pkg/aio_0cb4a19.css">
    <div>html of A</div>
    <div>html of B</div>
    <div>html of C</div>
</html>

静态资源管理系统可以十分灵活的适应各种性能优化场景,我们还可以统计 {widget} 插件的调用情况,然后自动生成最优的打包配置,让网站可以自适应优化,这样工程师不用关心资源在哪,怎么来的,怎么没的,所有资源定位的事情,都交给静态资源管理系统就好了。静态资源路径都带 md5 戳,这个值只跟内容有关,静态资源服务器从此可以放心开启强缓存了!还能实现静态资源的分级发布,轻松回滚!我们还可以继续研究,比如根据国际化、皮肤,终端等信息约定一种资源路径规范,当后端适配到特定地区、特定机型的访问时,静态资源管理系统帮你送达不同的资源给不同的用户。说到这里,大家应该比较清楚整个“一体化”的模块化解决方案了,有人可能会问,这样做岂不是增加了后端性能开销?对于这个问题,我们实践过的经验是,这非常值得!其实这个后端开销很少,算法非常简单直白,但他所换来的前端工程化水平提高非常大!

打包配置:

模块化基础架构

fire适用于投统计、异常处理等场景,不会触发代码加载。

前端开发领域(JavaScript、CSS、Template)并没有为开发者们提供以一种简洁、有条理地的方式来管理模块的方法。CommonJS(致力于设计、规划并标准化 JavaScript API)的诞生开启了“ JavaScript 模块化的时代”。CommonJS 的模块提案为在服务器端的 JavaScript 模块化做出了很大的贡献,但是在浏览器下的 JavaScript 模块应用很有限。随之而来又诞生了其它前端领域的模块化方案,像 requireJS、SeaJS 等,然而这些模块化方案并不是十分适用 ,并没有从根本上解决模块化的问题。

基础层是对一些基础模块和session数据的封装,这些方法每个模块都会用到,就不再做单独的require,打包成ctx变量传入每个模块的定义中进行使用。

JavaScript 模块化并不等于异步模块化

主流的 JavaScript 模块化方案都使用“异步模块定义”的方式,这种方式给开发带来了极大的不便,所有的同步代码都需要修改为异步的方式,我们是否可以在前端开发中使用“ CommonJS ”的方式,开发者可以使用自然、容易理解的模块定义和调用方式,不需要关注模块是否异步,不需要改变开发者的开发行为。

如何鉴定文件加载的结束,特别是CSS文件的加载结束是个蛮有学问的事情。在框架中对于文件的下载监控,我们借鉴了SeaJS的思想,对于这块感兴趣的同学可以深入研究一下。

编译工具

自动化工具会扫描目录下的模块进行编译处理并输出产出文件:

静态资源,经过编译处理过的 JavaScript、CSS、Image 等文件,部署在 CDN 服务器自动添加闭包,我们希望工程师在开发 JavaScript 模块的时候不需要关心” define ”闭包的事情,所以采用工具自动帮工程师添加闭包支持,例如如上定义的 nav.js 模块在经过自动化工具处理后变成如下,

define('common:widget/nav/nav.js', function( require, exports, module ) {
    // common/widget/nav/nav.js
    var $ = require('common:widget/jquery/jquery.js');

    exports.init = function() {
        ...
    };
});

模板文件,经过编译处理过的 smarty 文件,自动部署在模板服务器

资源表,记录每个静态资源的部署路径以及依赖关系,用于静态资源加载框架 静态资源加载框架(SR Management System)会加载 source maps 拿到页面所需要的所有模块以及静态资源的 url,然后组织资源输出最终页面。

这对于开发和测试的效率提升不言而喻。

模块化为打包部署带来的极大不便

传统的模块化方案更多的考虑是如何将代码进行拆分,但是当我们部署上线的时候需要将静态资源进行合并(打包),这个时候会发现困难重重,每个文件里只能有一个模块,因为模块使用的是“匿名定义”,经过一番研究,我们会发现一些解决方案,无论是“ combo 插件”还是“ flush 插件”,都需要我们修改模块化调用的代码,这无疑是雪上加霜,开发者不仅仅需要在本地开发关注模块化的拆分,在调用的时候还需要关注在一个请求里面加载哪些模块比较合适,模块化的初衷是为了提高开发效率、降低维护成本,但我们发现这样的模块化方案实际上并没有降低维护成本,某种程度上来说使得整个项目更加复杂了。

通用层是业务逻辑无关的组件,很多产品都可以复用,如分页、截字、弹层、类定义等。

CSS 模块

在模板模块中以及 JS 模块中对应同名的 CSS 模块会自动与模板模块、JS 模块添加依赖关系,进行加载管理,用户不需要显示进行调用加载。那么如何在一个 CSS 模块中声明对另一个 CSS 模块的依赖关系呢,我们可以通过在注释中的@require 字段标记的依赖关系,这些分析处理对 html 的 style 标签内容同样有效,

/**
 * demo.css
 * @require reset.css
 */

编辑:金莎娱乐app 本文来源:都需要加载JS、CSS甚至大量第三方类库,模块化是

关键词: