快捷搜索:

金沙第一娱乐娱城官网

当前位置:金沙第一娱乐娱城官网 > 金沙第一娱乐娱城官网 > 常用JS文件加载使用,开发人员在写代码的时候不

常用JS文件加载使用,开发人员在写代码的时候不

来源:http://www.dlksamusic.com 作者:金沙第一娱乐娱城官网 时间:2020-03-17 05:05

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

概述

随着页面表现形式越来越丰富,用户体验与交互越来越被关注。前端开发大量涌现出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的转换生成一份浏览器能够理解的生产代码。

金沙娱场app下载 1

前端构建工具

什么是webpack呢?

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

金沙娱场app下载 2

WebPack

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

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

金沙娱场app下载 3

webpack vs gulp

为什么需要webpack呢?

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

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

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

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

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

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

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

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

概念

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

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

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

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

因为长期的产品策略变更,导致代码不同功能块之间耦合严重,一些关键方法不知道都在哪里被调用过,如果修改的时候有遗漏,就会出现问题。

入口(entry)

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

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

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

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

思考:

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

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

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

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

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

这次重构迫切要解决的问题有三点:

一体化的前端模块化实践方案

写到这里,其实我们的“前端工程之块化”才正式开始,本文面向对前端模块化开发有所实践或有所研究的同学,接下来我们所介绍的前端模块化解决方案, 有别于 JavaScript 模块化方案或 CSS 模块化方案,它是一种可以综合处理前端各种资源的模块化方案;它可以极大提升开发者的开发体验,并为性能优化提供良好的支持。下面让我们来进一步来了解什么是“一体化”的模块化实践方案。

首先我们来看一下一个 web 项目是如何通过“一体化”的模块化方案来划分目录结构:

金沙娱场app下载 4

  • 站点(site):一般指能独立提供服务,具有单独二级域名的产品线。如旅游产品线或者特大站点的子站点(lv.baidu.com)。
  • 子系统(module):具有较清晰业务逻辑关系的功能业务集合,一般也叫系统子模块,多个子系统构成一个站点。子系统(module)包括两类: common 子系统, 为其他业务子系统提供规范、资源复用的通用模块;业务子系统:,根据业务、URI 等将站点进行划分的子系统站点。
  • 页面(page): 具有独立 URL 的输出内容,多个页面一般可组成子系统。
  • 模块(widget):能独立提供功能且能够复用的模块化代码,根据复用的方式不同分为 Template 模块、JS 模块、CSS 模块三种类型。
  • 静态资源(static):非模块化资源目录,包括模板页面引用的静态资源和其他静态资源(favicon,crossdomain.xml 等)。

前端模块(widget),是能独立提供功能且能够复用的模块化代码,根据复用的方式不同分为 Template 模块、JS 模块、CSS 模块三种类型,CSS 组件,一般来说,CSS 模块是最简单的模块,它只涉及 CSS 代码与 HTML 代码; JS 模块,稍为复杂,涉及 JS 代码,CSS 代码和 HTML 代码。一般,JS 组件可以封装 CSS 组件的代码; Template 模块,涉及代码最多,可以综合处理 HTML、JavaScript、CSS 等各种模块化资源,一般情况,Template 会将 JS 资源封装成私有 JS 模块、CSS 资源封装成自己的私有 CSS 模块。下面我们来一一介绍这几种模块的模块化方案。

  1. 代码调用方式统一,希望加载方式可以对开发人员透明。

  2. 模块职责明确、模块之间依赖关系清晰。

  3. 按需加载的配置可以统一管理,不影响开发时的效率。

模板模块

我们可以将任何一段可复用的模板代码放到一个 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 模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。

这三点,让我很自然地就想到了JS模块化开发

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 用于处理首屏外的按需模块。

模块化开发:

CSS 模块

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

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

关于模块化框架和CommonJS规范,在网上有很多介绍的文章,我就不在这里赘述了。我只说最关键的一点:每个模块都有明确的定义,模块之间的依赖和调用必须通过require或use的形式。如下图是一个common/pop模块的示例代码:

非模块化资源

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

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

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

这种设计带给我们最大的好处是:规范了代码之间的调用方式,开发人员在写use的时候,不用担心这个模块是否已经被加载,所有的加载策略和打包策略都对他是透明的。

项目实例

下面我们来看一下在一个实际项目中,如果在通过页面来调用各种类型的 widget,首先是目录结构:

├── common
│   ├── fis-conf.js
│   ├── page
│   ├── plugin
│   ├── static
│   └── widget
└── photo
    ├── fis-conf.js
    ├── output
    ├── page
    ├── static
    ├── test
    └── widget

我们有两个子系统,一个 common 子系统(用作通用),一个业务子系统,page 目录用来存放页面,widget 目录用来存放各种类型的模块,static 用于存放非模块化的静态资源,首先我们来看一下 photo/page/index.tpl 页面的源码,

{extends file="common/page/layout/layout.tpl"}
{block name="main"}
    {require name="photo:static/index/index.css"}
    {require name="photo:static/index/index.js"}
    <h3>demo 1</h3>
    <button id="btn">Button</button>
    {script type="text/javascript"}
        // 同步调用 jquery
        var $ = require('common:widget/jquery/jquery.js');

        $('#btn').click(function() {
            // 异步调用 respClick 模块
            require.async(['/widget/ui/respClick/respClick.js'], function() {
                respClick.hello();
            });
        });
    {/script}

    // 调用 renderBox 模块
    {widget name="photo:widget/renderBox/renderBox.tpl"}
{/block}

第一处代码是对非模块化资源的调用方式;第二处是用 require 的方式调用一个 JavaScript 模块;第三处是通过 require.async 通过异步的方式来调用一个 JavaScript 模块;最后一处是通过 widget 语法来调用一个模板模块。 respclick 模块的源码如下:

exports.hello = function() {
    alert('hello world');
};

renderBox 模板模块的目录结构如下:

└── widget
    └── renderBox
        ├── renderBox.css
        ├── renderBox.js
        ├── renderBox.tpl
        └── shell.jpeg

虽然 renderBox 下面包括 renderBox.js、renderBox.js、renderBox.tpl 等多种模块,我们再调用的时候只需要一行代码就可以了,并不需要关注内部的依赖,以及各种模块的初始化问题。

模块依赖 & 自定义事件

模块化基础架构

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

总体架构

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

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

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

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

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

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

金沙娱场app下载 5

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

编译工具

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

静态资源,经过编译处理过的 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,然后组织资源输出最终页面。

本文由金沙第一娱乐娱城官网发布于金沙第一娱乐娱城官网,转载请注明出处:常用JS文件加载使用,开发人员在写代码的时候不

关键词: