胖蔡叨叨叨
你听我说

RequireJs 高级用法

胖蔡阅读(510)

从包中加载模块

RequireJS支持加载CommonJS Packages目录结构中的模块,但是需要指定一些其他配置才能使其正常工作。具体来说,它支持以下CommonJS Packages功能:

  • 软件包可以与模块名称/前缀关联。
  • 程序包配置可以为特定程序包指定以下属性:
    • name:程序包的名称(用于模块名称/前缀映射)
    • location:磁盘上的位置。位置相对于baseUrl配置值,除非它们包含协议或以反斜杠(/)开头。
    • main:某人对“ packageName”的要求时应使用的包内模块的名称。默认值为“ main”,因此仅当它不同于默认值时才指定它。该值是相对于包文件夹的。

重要笔记

  • 尽管软件包可以具有CommonJS目录布局,但模块本身应采用RequireJS可以理解的模块格式。规则的例外:如果您使用的是r.js节点适配器,则这些模块可以采用传统的CommonJS模块格式。如果需要将传统的CommonJS模块转换为RequireJS使用的异步模块格式,则可以使用CommonJS转换工具
  • 一次只能在项目上下文中使用软件包的一个版本。您可以使用RequireJS多版本支持来加载两个不同的模块上下文,但是如果要在一个上下文中使用程序包A和B,并且它们依赖于程序包C的不同版本,那么这将是一个问题。将来可能会改变。

如果您使用《入门指南》中指定的类似项目布局,则Web项目的开始将类似于以下内容(基于Node / Rhino的项目是类似的,只需使用scripts目录的内容作为顶级项目目录):

  • 项目目录/
    • project.html
    • 脚本/
      • require.js

这是带有两个包cartstore的示例目录布局的外观:

  • 项目目录/
    • project.html
    • 脚本/
      • 大车/
        • main.js
      • 店铺/
        • main.js
        • util.js
      • main.js
      • require.js

project.html将具有如下脚本标记:

<script data-main="scripts/main" src="scripts/require.js"></script>

这将指示require.js加载脚本/main.js。main.js使用“ packages”配置来设置与require.js相关的软件包,在这种情况下,它们是源文件“ cart”和“ store”:

//main.js contents
//Pass a config object to require
require.config({
    "packages": ["cart", "store"]
});

require(["cart", "store", "store/util"],
function (cart,   store,   util) {
    //use the modules as usual.
});

“ cart”的需求意味着它将从scripts / cart / main.js加载,因为“ main”是RequireJS支持的默认主模块设置。将从scripts / store / util.js加载“ store / util”的需求。

如果“ store”包未遵循“ main.js”约定,则看起来更像这样:

  • 项目目录/
    • project.html
    • 脚本/
      • 大车/
        • main.js
      • 店铺/
        • store.js
        • util.js
      • main.js
      • package.json
      • require.js

然后,RequireJS配置将如下所示:

require.config({
    packages: [
        "cart",
        {
            name: "store",
            main: "store"
        }
    ]
});

为避免冗长,强烈建议始终使用在结构中使用“主要”约定的软件包。

多版本支持

配置选项中所述,可以使用不同的“上下文”配置选项将模块的多个版本加载到页面中。require.config()返回一个使用上下文配置的require函数。这是一个加载两个不同版本的alpha和beta模块的示例(此示例摘自其中一个测试文件):

<script src="../require.js"></script>
<script>
var reqOne = require.config({
  context: "version1",
  baseUrl: "version1"
});

reqOne(["require", "alpha", "beta",],
function(require,   alpha,   beta) {
  log("alpha version is: " + alpha.version); //prints 1
  log("beta version is: " + beta.version); //prints 1

  setTimeout(function() {
    require(["omega"],
      function(omega) {
        log("version1 omega loaded with version: " +
             omega.version); //prints 1
      }
    );
  }, 100);
});

var reqTwo = require.config({
      context: "version2",
      baseUrl: "version2"
    });

reqTwo(["require", "alpha", "beta"],
function(require,   alpha,   beta) {
  log("alpha version is: " + alpha.version); //prints 2
  log("beta version is: " + beta.version); //prints 2

  setTimeout(function() {
    require(["omega"],
      function(omega) {
        log("version2 omega loaded with version: " +
            omega.version); //prints 2
      }
    );
  }, 100);
});
</script>

注意,“ require”被指定为模块的依赖项。这允许传递给函数回调的require()函数使用正确的上下文正确加载模块以支持多版本。如果未将“ require”指定为依赖项,则可能会出现错误。

页面加载后加载代码

上面“ Multiversion支持”部分中的示例显示了以后如何通过嵌套的require()调用来加载代码。

网络工作者支持

从0.12版开始,RequireJS可以在Web Worker中运行。只需在网络工作者中使用importScripts()来加载require.js(或包含require()定义的JS文件),然后调用require。

您可能需要设置baseUrl 配置选项,以确保require()可以找到要加载的脚本。

通过查看单元测试中使用的文件之一,可以看到其用法示例。

犀牛支持§4.5

RequireJS可以通过r.js适配器在Rhino中使用。有关更多信息,请参见r.js自述文件

Nashorn支持

从RequireJS 2.1.16开始,RequireJS可以通过r.js适配器在Java 8+的JavaScript引擎Nashorn中使用。有关更多信息,请参见r.js自述文件

处理错误

错误的一般类别是脚本(未找到),网络超时或所加载脚本中的错误的404。RequireJS有一些用于处理它们的工具:特定于需求的errback,“ paths”数组配置以及全局的requirejs.onError。

传递给errbacks的错误对象和全局requirejs.onError函数通常将包含两个自定义属性:

  • requireType:具有一般分类的字符串值,例如“ timeout”,“ nodefine”,“ scripterror”。
  • requireModules:超时的模块名称/ URL的数组。

如果您在requireModules中遇到错误,则可能意味着未定义依赖于requireModules数组中的模块的其他模块。

捕获IE中的负载故障

Internet Explorer存在一系列问题,使得难以检测到错误/路径后备的加载失败:

  • script.onerror在IE 6-8中不起作用。无法知道加载脚本是否会生成404,更糟糕的是,即使在404情况下,它也会触发具有完整状态的onreadystatechange。
  • script.onerror确实可以在IE 9+中运行,但是存在一个错误,即在执行脚本后不立即触发script.onload事件处理程序,因此它不支持允许匿名AMD模块的标准方法。因此仍使用script.onreadystatechange。但是,在script.onerror函数启动之前,onreadystatechange会以完整状态启动。

因此,使用IE很难同时允许匿名的AMD模块和可靠的检测错误,匿名的AMD模块是AMD模块的核心优势。

但是,如果您知道在一个项目中使用define()声明其所有模块,或者它使用shim config为不使用define()的任何内容指定字符串导出,那么如果您设置了defineDefine配置值确实,加载器可以通过检查define()调用或填充程序的导出全局值的存在来确认脚本是否已加载。

因此,如果要支持Internet Explorer,捕获负载错误并通过直接define()调用或shim config获得模块化代码,请始终将forcedDefine设置为true。有关示例,请参见下一部分。

注意:如果您确实设置了forceDefine:true,并且使用data-main =“”来加载主JS模块,则该主JS模块必须调用define()而不是require()来加载所需的代码。JS主模块仍然可以调用require / requirejs来设置配置值,但是对于加载模块,它应该使用define()。

如果然后您还使用杏仁来构建没有require.js的代码,请确保使用insertRequire构建设置为主模块插入一个require调用-达到与最初的require()调用相同的目的,即调用data-main做。

require([])错误

当与requirejs.undef()一起使用时,Errbacks将允许您检测模块是否无法加载,取消定义该模块,将配置重置到另一个位置,然后重试。

一个常见的用例是使用CDN托管的库版本,但是如果失败,请切换到本地加载文件:

requirejs.config({
    enforceDefine: true,
    paths: {
        jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min'
    }
});

//Later
require(['jquery'], function ($) {
    //Do something with $ here
}, function (err) {
    //The errback, error callback
    //The error has a list of modules that failed
    var failedId = err.requireModules && err.requireModules[0];
    if (failedId === 'jquery') {
        //undef is function only on the global requirejs object.
        //Use it to clear internal knowledge of jQuery. Any modules
        //that were dependent on jQuery and in the middle of loading
        //will not be loaded yet, they will wait until a valid jQuery
        //does load.
        requirejs.undef(failedId);

        //Set the path to jQuery to local path
        requirejs.config({
            paths: {
                jquery: 'local/jquery'
            }
        });

        //Try again. Note that the above require callback
        //with the "Do something with $ here" comment will
        //be called if this new attempt to load jQuery succeeds.
        require(['jquery'], function () {});
    } else {
        //Some other error. Maybe show message to the user.
    }
});

使用`requirejs.undef()`,如果您稍后设置其他配置并尝试加载相同的模块,则加载器仍会记住哪些模块需要该依赖关系,并在新配置的模块加载时完成加载。

注意:errbacks仅适用于回调样式的require调用,而不适用define()调用。define()仅用于声明模块。

路径配置后备

上面的用于检测负载故障,对模块进行undef(),修改路径和重新加载的模式是一个足够常见的请求,它也有一个简写。路径配置允许使用数组值:

requirejs.config({
    //To get timely, correct error triggers in IE, force a define/shim exports check.
    enforceDefine: true,
    paths: {
        jquery: [
            'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
            //If the CDN location fails, load from this location
            'lib/jquery'
        ]
    }
});

//Later
require(['jquery'], function ($) {
});

上面的代码将尝试CDN位置,但是如果失败,则退回到本地lib / jquery.js位置。

注意:路径回退仅适用于确切的模块ID匹配。这与可应用于模块ID前缀段的任何部分的常规路径配置不同。后备的目标更多是针对异常错误的恢复,而不是通用的路径搜索路径解决方案,因为它们在浏览器中效率低下。

全局requirejs.onError函数

要检测本地错误未捕获的错误,可以覆盖requirejs.onError():

requirejs.onError = function (err) {
    console.log(err.requireType);
    if (err.requireType === 'timeout') {
        console.log('modules: ' + err.requireModules);
    }

    throw err;
};

RequireJS 配置选项

胖蔡阅读(220)

在顶层HTML页面(或未定义模块的顶层脚本文件)中使用require()时,可以将配置对象作为第一个选项传递:

<script src="scripts/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });
  require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
        //This function will be called when all the dependencies
        //listed above are loaded. Note that this function could
        //be called before the page is loaded.
        //This callback is optional.
    }
  );
</script>

您也可以从数据主入口点调用require.config ,但是要注意,数据主脚本是异步加载的。避免使用其他入口点脚本,这些脚本错误地假定data-main及其require.config将始终在脚本加载之前执行。

另外,可以require 加载require.js之前将config对象定义为全局变量,并自动应用值。此示例指定了一些要在require.js定义require()后立即加载的依赖项:

<script>
    var require = {
        deps: ["some/module1", "my/module2", "a.js", "b.js"],
        callback: function(module1, module2) {
            //This function will be called when all the dependencies
            //listed above in deps are loaded. Note that this
            //function could be called before the page is loaded.
            //This callback is optional.
        }
    };
</script>
<script src="scripts/require.js"></script>

注意:最好使用var require = {}并且不要使用 window.require = {},它将在IE中无法正确运行。

一些模式可以将配置与主模块加载分开

支持的配置选项:

baseUrl:用于所有模块查找的根路径。因此,在上面的示例中,“ my / module”的脚本标签将具有src =“ / another / path / my / module.js”。是的baseUrl装入普通.js文件时使用的(由一个依赖字符串指示开始以斜线,具有协议,或在端部的.js),这些字符串被原样使用,所以a.js和b.js将从与包含上述代码段的HTML页面相同的目录中加载。

如果在配置中未显式设置baseUrl,则默认值将是加载require.js的HTML页面的位置。如果使用data-main属性,则该路径将成为baseUrl。

baseUrl可以是与将加载require.js的页面不同的域上的URL。RequireJS脚本加载跨域工作。唯一的限制是文本加载的文本内容!插件:至少在开发过程中,这些路径应与页面位于同一域中。优化工具将内联文本!插件资源,因此在使用优化工具后,您可以使用引用文本的资源!来自另一个域的插件资源。

path:在baseUrl的正下方找不到模块名称的路径映射。除非路径设置以“ /”开头或其中包含URL协议(例如“ http:”),否则都假定该路径设置是相对于baseUrl的。使用上面的示例配置,“ some / module”的脚本标签将为src =“ / another / path / some / v1.0 / module.js”。

被用于模块名称应该路径包括扩展名,因为路径映射可能是一个目录。当将模块名称映射到路径时,路径映射代码将自动添加.js扩展名。如果使用了require.toUrl(),它将添加适当的扩展名(如果用于文本模板)。

在浏览器中运行时,可以指定路径回退,以允许尝试从CDN位置进行加载,但是如果CDN位置无法加载,则回退到本地位置。

bundles:在RequireJS 2.1.10中引入:允许配置要在另一个脚本中找到的多个模块ID。例子:

requirejs.config({
    bundles: {
        'primary': ['main', 'util', 'text', 'text!template.html'],
        'secondary': ['text!secondary.html']
    }
});

require(['util', 'text'], function(util, text) {
    //The script for module ID 'primary' was loaded,
    //and that script included the define()'d
    //modules for 'util' and 'text'
});

该配置指出:模块“ main”,“ util”,“ text”和“ text!template.html”将通过加载模块ID“ primary”来找到。可以通过加载模块ID“ secondary”来找到模块​​“ text!secondary.html”。

这仅设置了在脚本中包含多个define()模块的模块中查找模块的位置。它不会自动将那些模块绑定到捆绑软件的模块ID。捆绑软件的模块ID仅用于查找模块集。

使用path config可能会发生类似的事情,但是它要复杂得多,并且path config路由不允许在其配置中使用加载程序插件资源ID,因为path config的值是路径段而不是ID。

如果执行构建且该构建目标不是现有的模块ID,或者如果已构建的JS文件中包含不应由加载程序插件加载的加载程序插件资源,则bundles config很有用。请注意,键和值是模块ID,而不是路径段。它们是绝对的模块ID,而不是像path configmap config这样的模块ID前缀。此外,bundle config与map config的不同之处在于map config是一对一的模块ID关系,其中bundle config用于将多个模块ID指向bundle的模块ID。

从RequireJS 2.2.0开始,优化器可以生成bundle config,并将其插入到顶层requirejs.config()调用中。有关更多详细信息,请参见bundlesConfigOutFile构建配置选项。

shim:为不使用define()声明依赖关系和设置模块值的较旧的传统“浏览器全局变量”脚本配置依赖关系,导出和自定义初始化。

这是一个例子。它需要RequireJS 2.1.0+,并假定在baseUrl目录中已安装了ribs.js,underscore.js和jquery.js。如果没有,那么您可能需要为它们设置路径配置:

requirejs.config({
    //Remember: only use shim config for non-AMD scripts,
    //scripts that do not already call define(). The shim
    //config will not work correctly if used on AMD scripts,
    //in particular, the exports and init config will not
    //be triggered, and the deps config will be confusing
    //for those cases.
    shim: {
        'backbone': {
            //These script dependencies should be loaded before loading
            //backbone.js
            deps: ['underscore', 'jquery'],
            //Once loaded, use the global 'Backbone' as the
            //module value.
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'foo': {
            deps: ['bar'],
            exports: 'Foo',
            init: function (bar) {
                //Using a function allows you to call noConflict for
                //libraries that support it, and do other cleanup.
                //However, plugins for those libraries may still want
                //a global. "this" for the function will be the global
                //object. The dependencies will be passed in as
                //function arguments. If this function returns a value,
                //then that value is used as the module export value
                //instead of the object found via the 'exports' string.
                //Note: jQuery registers as an AMD module via define(),
                //so this will not work for jQuery. See notes section
                //below for an approach for jQuery.
                return this.Foo.noConflict();
            }
        }
    }
});

//Then, later in a separate file, call it 'MyModel.js', a module is
//defined, specifying 'backbone' as a dependency. RequireJS will use
//the shim config to properly load 'backbone' and give a local
//reference to this module. The global Backbone will still exist on
//the page too.
define(['backbone'], function (Backbone) {
  return Backbone.Model.extend({});
});

在RequireJS 2.0。*中,shim配置中的“ exports”属性可能是函数而不是字符串。在这种情况下,它的功能与上面显示的“ init”属性相同。“ init”模式在RequireJS 2.1.0+中使用,因此exports可以将字符串值用于 forceDefine,但是一旦已知已加载库,就可以进行功能工作。

对于仅仅是jQuery或Backbone插件的“模块”,不需要导出任何模块值,shim配置可以只是一个依赖项数组:

requirejs.config({
    shim: {
        'jquery.colorize': ['jquery'],
        'jquery.scroll': ['jquery'],
        'backbone.layoutmanager': ['backbone']
    }
});

但是请注意,如果要在IE中进行404负载检测,以便可以使用路径回退或errbacks,则应提供字符串输出值,以便加载程序可以检查脚本是否实际加载(init的返回用于enforceDefine检查):

requirejs.config({
    shim: {
        'jquery.colorize': {
            deps: ['jquery'],
            exports: 'jQuery.fn.colorize'
        },
        'jquery.scroll': {
            deps: ['jquery'],
            exports: 'jQuery.fn.scroll'
        },
        'backbone.layoutmanager': {
            deps: ['backbone']
            exports: 'Backbone.LayoutManager'
        }
    }
});

“ shim”配置的重要说明:

  • 填充程序配置仅设置代码关系。要加载属于shim config或使用shim config的模块,需要一个正常的require / define调用。本身设置填充程序不会触发代码加载。
  • 仅将其他“填充程序”模块用作填充脚本的依赖性,或者将不具有依赖性的AMD库也创建全局变量(如jQuery或lodash)后调用define()。否则,如果您使用AMD模块作为匀场配置模块的依赖项,则在构建之后,可能要等到构建中的填充代码执行后,才能评估该AMD模块,否则会发生错误。最终的解决方法是将所有填充的代码升级为具有可选的AMD define()调用。
  • 从RequireJS 2.1.11开始,如果无法升级填充代码以使用AMD define()调用,则优化器具有 wrapShim构建选项,该选项将尝试自动将填充代码包装在define()中以进行构建。这改变了填充的依赖关系的范围,因此不能保证始终有效,但是,例如,对于依赖于AMD版本的Backbone的填充的依赖关系,它可能会有所帮助。
  • 对于AMD模块,不会调用init函数。例如,您不能使用shim init函数来调用jQuery的noConflict。请参阅映射模块以将noConflict 用于jQuery的替代方法。
  • 通过RequireJS在节点中运行AMD模块时,不支持Shim config(尽管可用于优化程序)。根据正在填充的模块,它可能会在Node中失败,因为Node与浏览器没有相同的全局环境。从RequireJS 2.1.7开始,它将在控制台中警告您不支持shim config,并且它可能会或可能不会起作用。如果您想禁止显示该消息,则可以通过requirejs.config({ suppress: { nodeShim: true }});

“ shim”配置的重要优化器说明

  • 您应该使用mainConfigFile构建选项来指定可在其中找到垫片配置的文件。否则,优化程序将不了解填充程序配置。另一个选项是在构建配置文件中复制填充程序配置。
  • 不要在构建中将CDN加载与shim config混合使用。示例方案:从CDN加载jQuery,但使用shim配置加载类似于jQuery的Backbone的普通版本。在进行构建时,请确保在内建文件中内联jQuery,并且不要从CDN加载它。否则,Backbone将内联到生成的文件中,并在CDN加载的jQuery加载之前执行。这是因为shim config只是延迟文件的加载,直到加载依赖项为止,但不对define进行任何自动包装。构建后,依赖关系已经内联,shim配置无法将non-define()代码的执行推迟到以后。定义()’ d模块在构建后就可以处理CDN加载的代码,因为它们将其源代码正确包装在define工厂函数中,直到加载依赖项后才执行。因此,课程:shim config是非模块化代码,传统代码的权宜之计。define()的模块更好。
  • 对于本地的多文件构建,上述CDN建议也适用。对于任何匀场脚本,必须在匀场脚本执行之前加载其依赖项。这意味着要么直接在包含填充脚本的buid层中构建其依赖关系,要么通过require([], function (){})调用加载其依赖关系,然后require([])对具有填充脚本的构建层进行嵌套调用。
  • 如果您使用uglifyjs缩小代码,请不要将uglify选项设置toplevel为true,或者如果使用命令 行不通-mt。该选项会破坏shim用于查找出口的全局名称。

map:对于给定的模块前缀,而不是使用给定的ID加载模块,而是替换一个不同的模块ID。

这种功能对于大型项目而言非常重要,因为大型项目可能有两组模块需要使用两个不同的’foo’版本,但它们仍需要彼此合作。

使用上下文支持的多版本支持是不可能的。另外,路径配置仅用于设置模块ID的根路径,而不用于将一个模块ID映射到另一个模块。

地图示例:

requirejs.config({
    map: {
        'some/newmodule': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

如果模块按如下方式布置在磁盘上:

  • foo1.0.js
  • foo1.2.js
  • 一些/
    • newmodule.js
    • oldmodule.js

当’some / newmodule’执行`require(’foo’)`时,它将获取foo1.2.js文件;当’some / oldmodule’执行`require(’foo’)`时,它将获取foo1.0。 js文件。

此功能仅适用于是真正的AMD模块的脚本,这些脚本调用define()并注册为匿名模块。另外,仅对映射配置使用绝对模块ID。相对ID(如'../some/thing')不起作用。

还支持“ *”映射值,这意味着“对于所有加载的模块,请使用此映射配置”。如果有更具体的地图配置,则该配置优先于星型配置。例子:


requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

表示对于除“ some / oldmodule”以外的任何模块,当需要“ foo”时,请改用“ foo1.2”。仅对于“ some / oldmodule”,当要求“ foo”时使用“ foo1.0”。

注意:使用map config进行构建时,需要将map config馈给优化器,并且build输出必须仍然包含requirejs config调用来设置map config。优化器在构建期间不会进行ID重命名,因为项目中的某些依赖项引用可能取决于运行时变量状态。因此,优化器不会在构建后使对映射配置的需求无效。

config:通常需要将配置信息传递给模块。该配置信息通常被称为应用程序的一部分,并且需要一种将其传递给模块的方法。在RequireJS中,这是通过requirejs.config()的config选项完成的。然后,模块可以通过请求特殊的依赖项“模块”并调用module.config()来读取该信息。例子:

requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

//bar.js, which uses simplified CJS wrapping:
//https://requirejs.org/docs/whyamd.html#sugar
define(function (require, exports, module) {
    //Will be the value 'large'
    var size = module.config().size;
});

//baz.js which uses a dependency array,
//it asks for the special module ID, 'module':
//https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#wiki-magic
define(['module'], function (module) {
    //Will be the value 'blue'
    var color = module.config().color;
});

要将config传递给package,请以该包中的主模块(而不是package ID)为目标:

requirejs.config({
    //Pass an API key for use in the pixie package's
    //main module.
    config: {
        'pixie/index': {
            apiKey: 'XJKDLNS'
        }
    },
    //Set up config for the "pixie" package, whose main
    //module is the index.js file in the pixie folder.
    packages: [
        {
            name: 'pixie',
            main: 'index'
        }
    ]
});

:配置来自CommonJS包的加载模块。有关更多信息,请参见软件包主题

nodeIdCompat:节点治疗模块IDexample.jsexample相同的。默认情况下,这是RequireJS中的两个不同ID。如果最终使用的是从npm安装的模块,则可能需要设置此配置值true以避免解析问题。此选项仅适用于以不同方式处理“ .js”后缀,它不执行任何其他节点解析和评估匹配,例如.json文件处理(JSON处理仍然需要“ json!”加载程序插件)。在2.1.10及更高版本中可用。

waitSeconds:放弃放弃加载脚本之前要等待的秒数。将其设置为0将禁用超时。默认值为7秒。

context:赋予加载上下文的名称。只要每个顶级require调用指定一个唯一的上下文字符串,这都允许require.js在页面中加载模块的多个版本。要正确使用它,请参阅“ Multiversion支持”部分。

deps:要加载的依赖项数组。当在require.js加载之前将require定义为配置对象时,并且您要指定要在require()定义后立即加载的依赖项时,此选项很有用。使用deps就像进行require([])调用一样,但是在加载程序处理完配置后立即使用。它不会阻止 其他任何require()调用启动对模块的请求,它只是指定某些模块作为config块的一部分异步加载的一种方式。

callback:加载deps后执行的函数。在将require.js加载之前,将require定义为配置对象,并且您希望在加载配置的deps数组之后指定要使用的函数时,此选项很有用。

:设置为true时,如果脚本加载时未调用define()或具有可检查的填充程序导出字符串值,则将引发错误。有关更多信息,请参见在IE中捕获负载故障

xhtml:如果设置为true,则document.createElementNS()将用于创建脚本元素。

urlArgs:附加到RequireJS用于获取资源的URL的额外查询字符串参数。在未正确配置浏览器或服务器时,最有用的方法是缓存崩溃。urlArgs的高速缓存半身设置示例:

urlArgs: "bust=" +  (new Date()).getTime()

从RequireJS 2.2.0开始,urlArgs可以是一个函数。如果是函数,它将接收模块ID和URL作为参数,并且应返回将添加到URL末尾的字符串。如果没有参数,则返回一个空字符串。请务必注意添加“?” 或“&”(取决于URL的现有状态)。例子:

requirejs.config({
    urlArgs: function(id, url) {
        var args = 'v=1';
        if (url.indexOf('view.html') !== -1) {
            args = 'v=2'
        }

        return (url.indexOf('?') === -1 ? '?' : '&') + args;
    }
});

在开发过程中,使用它可能会很有用,但是请确保在部署代码之前将其删除。

scriptType:指定type =“”属性的值,该属性用于RequireJS插入文档中的脚本标签。默认值为“文本/ javascript”。要使用Firefox的JavaScript 1.8功能,请使用“ text / javascript; version = 1.8”。

skipDataMain:在RequireJS 2.1.9中引入:如果设置为true,则跳过对数据主属性的扫描以开始加载模块。如果RequireJS嵌入在可以与页面上的其他RequireJS库进行交互的实用程序库中,并且嵌入的版本不应进行数据主加载,则很有用。

RequireJs API基本使用

胖蔡阅读(245)

加载JavaScript文件

与传统的<script>标记相比,RequireJS采用了不同的脚本加载方式。尽管它还可以快速运行并优化得很好,但主要目标是鼓励使用模块化代码。作为其一部分,它鼓励使用模块ID代替脚本标记的URL。

RequireJS加载相对于baseUrl的所有代码。通常,将baseUrl设置为与data-main属性中使用的脚本相同的目录,以使顶级脚本加载页面。该数据主要属性是一个特殊的属性,require.js将检查启动脚本加载。本示例将以脚本的baseUrl结尾:

<!--This sets the baseUrl to the "scripts" directory, and
    loads a script that will have a module ID of 'main'-->
<script data-main="scripts/main.js" src="scripts/require.js"></script>

或者,可以通过RequireJS config手动设置baseUrl。如果没有显式配置且未使用data-main,则默认的baseUrl是包含运行RequireJS的HTML页面的目录。

默认情况下,RequireJS还假定所有依赖项都是脚本,因此它不希望在模块ID上看到尾随的“ .js”后缀。在将模块ID转换为路径时,RequireJS将自动添加它。使用path config,可以设置一组脚本的位置。与传统的<script>标记相比,所有这些功能都允许您为脚本使用较小的字符串。

有时您可能确实想直接引用脚本,而又不遵循“ baseUrl +路径”规则来查找脚本。如果模块ID具有以下特征之一,则该ID将不会通过“ baseUrl +路径”配置传递,而只会被视为与文档相关的常规URL:

  • 以“ .js”结尾。
  • 以“ /”开头。
  • 包含URL协议,例如“ http:”或“ https:”。

通常,最好使用baseUrl和“ paths”配置来设置模块ID的路径。这样,它为您重命名和配置指向不同位置的路径以提供更大的灵活性,以进行优化构建。

同样,为避免进行大量配置,最好避免对脚本使用较深的文件夹层次结构,而是将所有脚本都保留在baseUrl中,或者如果要将库/供应商提供的代码与应用程序代码分开,请使用像这样的目录布局:

  • 万维网/
    • index.html
    • js /
      • 应用程序/
        • sub.js
      • lib /
        • jquery.js
        • canvas.js
      • app.js
      • require.js

在index.html中:

<script data-main="js/app.js" src="js/require.js"></script>

并在app.js中:

requirejs.config({
    //By default load any module IDs from js/lib
    baseUrl: 'js/lib',
    //except, if the module ID starts with "app",
    //load it from the js/app directory. paths
    //config is relative to the baseUrl, and
    //never includes a ".js" extension since
    //the paths config could be for a directory.
    paths: {
        app: '../app'
    }
});

// Start the main app logic.
requirejs(['jquery', 'canvas', 'app/sub'],
function   ($,        canvas,   sub) {
    //jQuery, canvas and the app/sub module are all
    //loaded and can be used here now.
});

请注意,作为该示例的一部分,像jQuery这样的供应商库在文件名中没有其版本号。如果要跟踪版本信息,建议将其存储在单独的文本文件中,或者如果使用诸如volo之类的工具,它将在package.json中标记版本信息,但将文件保留在磁盘上为“ jquery”。 js”。这使您可以进行非常小的配置,而不必在每个库的“路径”配置中放置一个条目。例如,将“ jquery”配置为“ jquery-1.7.2”。

理想情况下,您加载的脚本将是通过调用define()定义的模块。但是,您可能需要使用某些传统/旧版的“浏览器全局变量”脚本,这些脚本无法通过define()表达它们的依赖性。对于那些,您可以使用shim config。正确表达其依赖性。

如果不表达依赖关系,则由于RequireJS异步加载脚本且速度不佳,可能会导致加载错误。

数据主入口点

data-main属性是一个特殊属性,require.js将检查该属性以开始脚本加载:

<!--when require.js loads it will inject another script tag
    (with async attribute) for scripts/main.js-->
<script data-main="scripts/main" src="scripts/require.js"></script>

通常,您将使用数据主脚本来设置配置选项,然后加载第一个应用程序模块。注意:为您的数据主模块生成的脚本标签require.js包含async属性。这意味着您不能假定数据主脚本的加载和执行将在同一页面稍后引用的其他脚本之前完成。

例如,当在稍后需要require()之前未设置’foo’模块的require.config路径时,这种安排将随机失败:

<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>
// contents of main.js:
require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});
// contents of other.js:

// This code might be called before the require.config() in main.js
// has executed. When that happens, require.js will attempt to
// load 'scripts/foo.js' instead of 'scripts/libs/foo-1.1.3.js'
require(['foo'], function(foo) {

});

如果要require()在HTML页面中进行调用,则最好不要使用data-main。data-main仅在页面只有一个主要入口点即data-main脚本时使用。对于要进行内联require()调用的页面,最好将那些页面嵌套require()在配置调用中:

<script src="scripts/require.js"></script>
<script>
require(['scripts/config'], function() {
    // Configuration loaded now, safe to do other require calls
    // that depend on that config.
    require(['foo'], function(foo) {

    });
});
</script>

定义模块

模块与传统脚本文件的不同之处在于,它定义了一个范围广泛的对象,可避免污染全局名称空间。它可以显式列出其依赖关系,并在无需引用全局对象的情况下获取这些依赖关系的句柄,而是将依赖关系作为定义模块的函数的参数来接收。RequireJS中的模块Module Pattern的扩展,其优点是不需要全局变量来引用其他模块。

模块的RequireJS语法允许它们以尽可能快的速度加载,即使顺序混乱也可以,但是以正确的依赖关系顺序进行评估,并且由于未创建全局变量,因此可以在页面中加载模块的多个版本

(如果您熟悉或正在使用CommonJS模块,那么也请参阅CommonJS Notes以获取有关RequireJS模块格式如何映射到CommonJS模块的信息)。

磁盘上每个文件应该只有一个模块定义。可以通过优化工具将模块分组为优化的捆绑包。

简单名称/值对

如果模块没有任何依赖关系,而只是名称/值对的集合,则只需将对象文字传递给define():

//Inside file my/shirt.js:
define({
    color: "black",
    size: "unisize"
});

定义功能

如果模块没有依赖项,但是需要使用函数来完成一些设置工作,则定义自己,然后将函数传递给define():

//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
    //Do setup work here

    return {
        color: "black",
        size: "unisize"
    }
});

具有依赖关系的定义函数

如果模块具有依赖项,则第一个参数应为依赖项名称数组,第二个参数应为定义函数。加载所有依赖项后,将调用该函数以定义模块。该函数应返回定义模块的对象。依赖项将作为函数参数传递给定义函数,并以与依赖项数组中的顺序相同的顺序列出:

//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
        //return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

在此示例中,创建了我/衬衫模块。这取决于我/购物车和我/库存。在磁盘上,文件的结构如下:

  • 我/cart.js
  • 我/inventory.js
  • 我的/shirt.js

上面的函数调用指定了两个参数:“购物车”和“库存”。这些是由“ ./cart”和“ ./inventory”模块名称表示的模块。

在加载了my / cart和my / inventory模块之后,该函数才被调用,并且该函数将这些模块作为“ cart”和“ inventory”自变量接收。

明确建议不要使用定义全局变量的模块,以便一次在一个页面中可以存在一个模块的多个版本(请参阅高级用法)。此外,函数参数的顺序应与依赖项的顺序匹配。

函数调用的返回对象定义“我/衬衫”模块。通过以这种方式定义模块,“ my / shirt”就不会作为全局对象存在。

将模块定义为函数

模块不必返回对象。允许来自函数的任何有效返回值。这是一个返回函数作为其模块定义的模块:

//A module definition inside foo/title.js. It uses
//my/cart and my/inventory modules from before,
//but since foo/title.js is in a different directory than
//the "my" modules, it uses the "my" in the module dependency
//name to find them. The "my" part of the name can be mapped
//to any directory, but by default, it is assumed to be a
//sibling to the "foo" directory.
define(["my/cart", "my/inventory"],
    function(cart, inventory) {
        //return a function to define "foo/title".
        //It gets or sets the window title.
        return function(title) {
            return title ? (window.title = title) :
                   inventory.storeName + ' ' + cart.name;
        }
    }
);

使用简化的CommonJS包装器定义模块

果您想重用以传统CommonJS模块格式编写的某些代码,则可能难以对上面使用的依赖项数组进行重新处理,并且您可能更希望将依赖项名称直接与用于此的本地变量对齐依赖性。在以下情况下,可以使用简化的CommonJS包装器

define(function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        //Return the module value
        return function () {};
    }
);

该包装器依靠Function.prototype.toString()给出函数内容的有用字符串值。在某些设备(例如PS3和某些较旧的Opera移动浏览器)上,此功能不起作用。使用优化器以数组格式提取依赖项,以在这些设备上使用。

有关更多信息,请参见CommonJS页面,以及“为什么选择AMD”页面中的“糖”部分

用名称定义模块

您可能会遇到一些define()调用,其中包含模块的名称作为define()的第一个参数:

    //Explicitly defines the "foo/title" module:
    define("foo/title",
        ["my/cart", "my/inventory"],
        function(cart, inventory) {
            //Define foo/title object in here.
       }
    );

这些通常是由优化工具生成的。您可以自己明确命名模块,但这会使模块的可移植性降低-如果将文件移动到另一个目录,则需要更改名称。通常最好避免为模块的名称编码,而让优化工具以模块名称进行刻录。优化工具需要添加名称,以便一个文件中可以捆绑多个模块,以允许在浏览器中更快地加载。

其他模块说明

每个文件一个模块。注意:鉴于模块名称到文件路径查找算法的性质,每个JavaScript文件只能定义一个模块。您仅应使用优化工具将多个模块分组为优化文件。

define()中的相对模块名称:对于在define()函数调用中可能发生的require(“ ../ relative / name”)调用,请务必要求“ require”作为依赖项,以便解析相对名称正确地:

define(["require", "./relative/name"], function(require) {
    var mod = require("./relative/name");
});

更妙的是,使用可用于翻译CommonJS模块的缩短的语法:

define(function(require) {
    var mod = require("./relative/name");
});

这种形式将使用Function.prototype.toString()查找require()调用,并将它们与“ require”一起添加到依赖项数组中,因此代码将在相对路径下正常工作。

如果要在目录中创建一些模块,则相对路径非常有用,这样您就可以与其他人或其他项目共享目录,并且希望能够获得该目录中同级模块的句柄,而不必知道目录的名称。

相对模块名称相对于其他名称,而不是路径:加载程序按模块名称而不是内部路径存储模块。因此,对于相对名称引用,将相对于作为引用的模块名称进行解析,然后,如果需要加载,则将该模块名称或ID转换为路径。其中具有“ main”和“ extras”模块的“ compute”包的示例代码:

* lib/
    * compute/
        * main.js
        * extras.js

main.js模块如下所示:

define(["./extras"], function(extras) {
    //Uses extras in here.
});

如果这是路径配置:

require.config({
    baseUrl: 'lib',
    paths: {
      'compute': 'compute/main'
    }
});

并且require(['compute'])做了,那么LIB /计算/ main.js将有“计算”的模块名称。当它要求“ ./extras”时,相对于“ compute”可以解决,因此“ compute /./ extras”将其标准化为“ extras”。由于没有用于该模块名称的路径配置,因此生成的路径将是针对“ lib / extras.js”的,这是不正确的。

在这种情况下,packages config是一个更好的选择,因为它允许将主模块设置为“计算”,但是在内部加载程序将存储ID为“ compute / main”的模块,以便“ ./演员的作品。

另一个选择是在lib / compute.js处构建一个模块just define(['./compute/main'], function(m) { return m; });,那么就不需要路径或程序包配置了。

或者,不要设置路径或程序包的配置,并且不要在顶层要求调用as require(['compute/main'])

生成相对于模块的URL:您可能需要生成相对于模块的URL。为此,要求“ require”作为依赖项,然后使用require.toUrl()生成URL:

define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});

控制台调试:如果需要使用已经通过require(["module/name"], function(){})JavaScript控制台中的调用加载的模块,则可以使用require()形式,该形式仅使用模块的字符串名称来获取它:

require("module/name").callSomeFunction()

请注意,这仅在先前通过require:的异步版本加载“模块/名称”时有效require(["module/name"])。如果使用相对路径,例如“ ./module/name”,则这些仅在内部定义

循环依赖

如果定义循环依赖关系(“ a”需要“ b”,“ b”需要“ a”),则在这种情况下,当调用“ b”的模块函数时,它将获得“ a”的未定义值。在使用require()方法定义模块之后,“ b”可以稍后再获取“ a”(请确保将require指定为依赖项,以便使用正确的上下文来查找“ a”):

//Inside b.js:
define(["require", "a"],
    function(require, a) {
        //"a" in this case will be null if "a" also asked for "b",
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

通常,您不需要使用require()来获取模块,而是依靠将模块作为参数传递给函数。循环依赖关系很少见,通常是您可能需要重新考虑设计的信号。但是,有时需要它们,在这种情况下,请使用上面指定的require()。

如果您熟悉CommonJS模块,则可以使用导出为该模块创建一个空对象,该空对象可立即供其他模块引用。通过在循环依赖关系的两边执行此操作,然后可以安全地保留另一个模块。仅当每个模块都为模块值而不是函数导出对象时,此方法才有效:

//Inside b.js:
define(function(require, exports, module) {
    //If "a" has used exports, then we have a real
    //object reference here. However, we cannot use
    //any of "a"'s properties until after "b" returns a value.
    var a = require("a");

    exports.foo = function () {
        return a.bar();
    };
});

或者,如果您使用的是依赖项数组方法,则要求特殊的 “ exports”依赖项:

//Inside b.js:
define(['a', 'exports'], function(a, exports) {
    //If "a" has used exports, then we have a real
    //object reference here. However, we cannot use
    //any of "a"'s properties until after "b" returns a value.

    exports.foo = function () {
        return a.bar();
    };
});

指定JSONP服务依赖关系

JSONP是使用JavaScript调用某些服务的一种方式。它跨域工作,并且是一种仅通过脚本标签仅需要HTTP GET即可调用服务的既定方法。

要在RequireJS中使用JSONP服务,请指定“ define”作为回调参数的值。这意味着您可以获取JSONP URL的值,就好像它是模块定义一样。

这是一个调用JSONP API端点的示例。在此示例中,JSONP回调参数称为“ callback”,因此“ callback = define”告诉API将JSON响应包装在“ define()”包装器中:

require(["http://example.com/api/data.json?callback=define"],
    function (data) {
        //The data object will be the API response for the
        //JSONP data call.
        console.log(data);
    }
);

JSONP的这种使用应限于初始应用程序设置的JSONP服务。如果JSONP服务超时,则意味着可能无法执行通过define()定义的其他模块,因此错误处理不可靠。

仅支持作为JSON对象的JSONP返回值。数组,字符串或数字的JSONP响应将不起作用。

此功能不应用于长轮询JSONP连接-处理实时流的API。这类API在收到每个响应后应该执行更多的脚本清理工作,而RequireJS将只获取一次JSONP URL-随后将相同的URL作为require()或define()调用中的依赖项使用,将获得一个缓存的值。

加载JSONP服务时,错误通常会通过服务超时显示出来,因为脚本标记加载不会为网络问题提供太多详细信息。要检测错误,可以重写requirejs.onError()以获取错误。在“处理错误”部分中有更多信息。

取消定义模块

有一个全局函数requirejs.undef(),它允许取消定义模块。它将重置加载程序的内部状态,以忽略模块的先前定义。

但是,它不会从其他已定义的模块中删除该模块,并且在执行时会将该模块作为依赖项获得该模块的句柄。因此,只有在没有其他模块无法处理该模块值的错误情况下使用它,或者作为将来可能使用该模块的模块加载的一部分,它才真正有用。有关示例,请参见errback部分

如果要进行更复杂的依赖关系图分析以进行未定义的工作,则半私有的 onResourceLoad API可能会有所帮助。

Gulp 项目报错:AssertionError: Task function must be specified

胖蔡阅读(324)

问题

运行gulp项目报错:

> Executing task: npx gulp dev <

[HPM] Proxy created: /services  ->  http://localhost:8081
AssertionError [ERR_ASSERTION]: Task function must be specified
    at Gulp.set [as _setTask] (D:\Projects\web\ptafrontGulp\node_modules\undertaker\lib\set-task.js:10:3)
    at Gulp.task (D:\Projects\web\ptafrontGulp\node_modules\undertaker\lib\task.js:13:8)
    at Object.<anonymous> (D:\Projects\web\ptafrontGulp\gulpfile.js:65:6)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at requireOrImport (D:\Projects\web\ptafrontGulp\node_modules\gulp-cli\lib\shared\require-or-import.js:19:11) {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '=='
}
终端进程“C:\Windows\System32\cmd.exe /d /c npx gulp dev”已终止,退出代码: 1。

解决

gulp项目需要全局安装gulp和项目内安装gulp,通过 gulp -v 查看全局gulp 和本地项目的gulp版本:

D:\Projects\web\ptafrontGulp>gulp -v
CLI version: 2.3.0
Local version: 4.0.2

那么如果CLI 版本和 Local 版本不一致,我们可以修改cli版本或者Local版本来解决这个问题。

让 CLI version 也变成 4.0.2 版本,可执行命令:

$npm install -g gulp@4.0.2

让 Local version 变成 2.3.0 版本,可执行命令:

$yarn add -D  gulp@2.3.0

VS Code中支持tpl、dwt文件

胖蔡阅读(614)

开发中使用Gulp + Browserify对于前端的多页面应用进行工程模块化,发现VS Code无法识别tpl文件,就找了VS Code 支持的方式,并记录下来。

打开 文件 =》首选项 =》设置,并输入associations:

加入上面的划线部分设置即可。

AMD 规范

胖蔡阅读(341)

本文是源仓库里的”AMD”文档的一份拷贝,放在这里是用来维护历史链接。文中任何与源仓库里的文档不一致之处,以源仓库里的文档为准。

异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

此AMD与科技公司AMD 及其制造的AMD处理器无关。

API说明

define() 函数

本规范只定义了一个函数 “define”,它是全局变量。函数的描述为:

    define(id?, dependencies?, factory);

id

名字

第一个参数,id,是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。

模块名的格式

模块名用来唯一标识定义中模块,它们同样在依赖数组中使用。AMD的模块名规范是CommonJS模块名规范的超集。引用如下:

  • 模块名是由一个或多个单词以正斜杠为分隔符拼接成的字符串
  • 单词须为驼峰形式,或者”.”,”..”
  • 模块名不允许文件扩展名的形式,如”.js”
  • 模块名可以为 “相对的” 或 “顶级的”。如果首字符为”.”或”..”则为”相对的”模块名
  • 顶级的模块名从根命名空间的概念模块解析
  • 相对的模块名从 “require” 书写和调用的模块解析

上文引用的CommonJS模块id属性常被用于JavaScript模块。

相对模块名解析示例:

  • 如果模块 "a/b/c" 请求 "../d", 则解析为"a/d"
  • 如果模块 "a/b/c" 请求 "./e", 则解析为"a/b/e"

如果AMD的实现支持加载器插件(Loader-Plugins),则”!”符号用于分隔加载器插件模块名和插件资源名。由于插件资源名可以非常自由地命名,大多数字符都允许在插件资源名使用。

(译注:关于Loader-Plugins

依赖

第二个参数,dependencies,是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。

依赖的模块名如果是相对的,应该解析为相对定义中的模块。换句话来说,相对名解析为相对于模块的名字,并非相对于寻找该模块的名字的路径。

本规范定义了三种特殊的依赖关键字。如果”require”,”exports”, 或 “module”出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。

依赖参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果工厂方法的形参个数小于3,加载器会选择以函数指定的参数个数调用工厂方法。

工厂方法

第三个参数,factory,为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。

简单的 CommonJS 转换

如果依赖性参数被忽略,模块加载器可以选择扫描工厂方法中的require语句以获得依赖性(字面量形为require(“module-id”))。第一个参数必须字面量为require从而使此机制正常工作。

在某些情况下,因为脚本大小的限制或函数不支持toString方法(Opera Mobile是已知的不支持函数的toString方法),模块加载器可以选择扫描不扫描依赖性。

如果有依赖参数,模块加载器不应该在工厂方法中扫描依赖性。

define.amd 属性

为了清晰的标识全局函数(为浏览器加载script必须的)遵从AMD编程接口,任何全局函数应该有一个”amd”的属性,它的值为一个对象。这样可以防止与现有的定义了define函数但不遵从AMD编程接口的代码相冲突。

当前,define.amd对象的属性没有包含在本规范中。实现本规范的作者,可以用它通知超出本规范编程接口基本实现的额外能力。

define.amd的存在表明函数遵循本规范。如果有另外一个版本的编程接口,那么应该定义另外一个属性,如define.amd2,表明实现只遵循该版本的编程接口。

一个如何定义同一个环境中允许多次加载同一个版本的模块的实现:

    define.amd = {
      multiversion: true
    };

最简短的定义:

    define.amd = {};

一次输出多个模块

在一个脚本中可以使用多次define调用。这些define调用的顺序不应该是重要的。早一些的模块定义中所指定的依赖,可以在同一脚本中晚一些定义。模块加载器负责延迟加载未解决的依赖,直到全部脚本加载完毕,防止没必要的请求。

例子

使用 require 和 exports

创建一个名为”alpha”的模块,使用了require,exports,和名为”beta”的模块:

   define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

一个返回对象的匿名模块:

   define(["alpha"], function (alpha) {
       return {
         verb: function(){
           return alpha.verb() + 2;
         }
       };
   });

一个没有依赖性的模块可以直接定义对象:

   define({
     add: function(x, y){
       return x + y;
     }
   });

一个使用了简单CommonJS转换的模块定义:

   define(function (require, exports, module) {
     var a = require('a'),
         b = require('b');

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

全局变量

本规范保留全局变量”define”以用来实现本规范。包额外信息异步定义编程接口是为将来的CommonJS API保留的。模块加载器不应在此函数添加额外的方法或属性。

本规范保留全局变量”require”被模块加载器使用。模块加载器可以在合适的情况下自由地使用该全局变量。它可以使用这个变量或添加任何属性以完成模块加载器的特定功能。它同样也可以选择完全不使用”require”。

使用注意

为了使静态分析工具(如build工具)可以正常工作,推荐使用字面上形如的’define(…)’。

与CommonJS的关系

一个关于本API的wiki开始在CommonJS wiki中创建了,作为中转的格式,模块中转。但是为了包含模块定义接口,随着时间而不断改变。在CommonJS列表中关于推荐本API作为模块定义API尚未达成一致。本API被转移到它自己的wiki和讨论组中。

AMD可以作为CommonJS模块一个中转的版本只要CommonJS没有被用来同步的require调用。使用同步require调用的CommonJS代码可以被转换为使用回调风格的AMD模块加载器。

JS 的模块化规范发展历程

胖蔡阅读(303)

JavaScript前期的代码还不是很复杂,我们可以通过代码简单实现,但随着项目越来越大、程序所包含的功能越来越多、开发人员原来越多的时候,我们就需要一个统一的模块化规范来编写代码,以求我们的代码的可读性、可维护性更高,同时也方便其他人来使用我们的实现。

什么是模块化?

模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。

解决目标

  • 提高代码的可维护性,降低重构成本
  • 解决全局变量污染和变量重名等问题
  • 依赖管理

缺点

  • 系统分层,调用链会很长
  • 模块间通信,模块间发送消息会很耗性能

模块化的演进

随着需求的不断增加,前端的模块化技术也是一直处于不断演进的状态。

全局function模式(1999)

将不同的功能封装成不同的全局函数,这会导致Global被污染了, 很容易引起命名冲突。

 function sum(a,b){
   return parseInt(a) + parseInt(b);
 }


  function reduce(a,b){
   return parseInt(a) - parseInt(b);
 }

命名空间

通过将参数、方法挂载在对象上,实现的简单的模块化隔离。

var MYNAMESPACE = MYNAMESPACE || {};
 
MYNAMESPACE.person = function(name) {
    this.name = name;
};
 
MYNAMESPACE.person.prototype.getName = function() {
    return this.name;
};
 
// 使用方法
var p = new MYNAMESPACE.person("doc");
p.getName();        /


IIFE执行函数(闭包模式)

IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。我看可以通过自执行函数实现数据的私有化隔离(函数上下文)。匿名函数自身不污染全局环境,同时为内部变量提供作用于环境空间,且提供闭包环境,可以做闭包想做的事情。

var a = 2;
(function IIFE(global){
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})(window);
 
console.log(a); // 2

CommonJS

2009年发布,Node 应用由模块组成,采用 CommonJS 模块规范。commonJS规范加载是同步的,也就是说,加载完成才执行后面的操作。CommonJS遵循Modules/1.0(http://wiki.commonjs.org/wiki/Modules/1.0)规范,该规范首次提出了JS的模块化实现应该遵循的规则(nodejs)。规范指出:

  1. 模块上下文(require)
  • 在模块中,有一个自由变量“ require”,即一个函数
  • 在模块中,有一个称为“ exports”的自由变量,该变量是模块执行时可以向其添加API的对象。
  • 模块必须使用“导出”对象作为唯一的导出方法。
  1. 模块标识符
  • 模块标识符是由正斜杠分隔的“条款”字符串。
  • 术语必须是驼峰标识符“。”或“ ..”。
  • 模块标识符可能没有文件扩展名,例如“ .js”。
  • 模块标识符可以是“相对”或“顶级”。如果第一项为“”,则模块标识符为“相对”。或者 ”..”。
  • 顶级标识符从概念模块名称空间根目录解析。
  • 相对标识符相对于在其中写入和调用“ require”的模块的标识符进行解析。
  1. 未指定

该规范保留了以下未指明的互操作性要点:

  • 模块是与数据库,文件系统或工厂功能一起存储的,还是与链接库可互换的。
  • 模块加载程序是否支持PATH来解析模块标识符。

遵循commonjs规范的代码:


//math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};


//increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};


//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2


Modules/1.0规范源于服务端,无法直接用于浏览器端,原因表现为:

  • 外层没有function包裹,变量全暴漏在全局。
  • 资源的加载方式与服务端完全不同。

浏览器模块化

基于Modules/1.0的基础上,commonJs社区讨论关于浏览器端的模块化规范的时候,内部发生了比较大的分歧,分裂出了三个主张,渐渐的形成三个不同的派别:

Modules/Transport

这一波人认为,在现有基础上进行改进即可满足浏览器端的需要,既然浏览器端需要function包装,需要异步加载,那么新增一个方案,能把现有模块转化为适合浏览器端的就行了。基于这个主张,制定了Modules/Transport(http://wiki.commonjs.org/wiki/Modules/Transport)规范,提出了先通过工具把现有模块转化为复合浏览器上使用的模块,然后再使用的方案。

AMD

AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。他们认为浏览器与服务器环境差别太大,不能沿用旧的模块标准。既然浏览器必须异步加载代码,那么模块在定义的时候就必须指明所依赖的模块,然后把本模块的代码写在回调函数里。模块的加载也是通过下载-回调这样的过程来进行,这个思想就是AMD的基础,由于“革新派”与“保皇派”的思想无法达成一致,最终从CommonJs中分裂了出去,独立制定了浏览器端的js模块化规范AMD(Asynchronous Module Definition)(https://github.com/amdjs/amdjs-api/wiki/AMD)

// 规定采用require语句加载模块,但是不同于CommonJS,它要求两个参数
require([module], callback);
// 在定义模块的时候需要使用define函数定义:
define(id?, dependencies?, factory);
  • 动态并行加载js,依赖前置,一个模块的回调函数必须得等到所有依赖都加载完毕之后,才可执行。无需再考虑js加载顺序问题。
  • 规范化输入输出,使用起来方便。
  • 对于不满足AMD规范的文件可以很好地兼容。

Modules/Wrappings

这一波人有点像“中间派”,既不想丢掉旧的规范,也不想像AMD那样推到重来。他们认为,Modules/1.0固然不适合浏览器,但它里面的一些理念还是很好的,(如通过require来声明依赖),新的规范应该兼容这些,AMD规范也有它好的地方(例如模块的预先加载以及通过return可以暴漏任意类型的数据,而不是像commonjs那样exports只能为object),也应采纳。最终他们制定了一个Modules/Wrappings(http://wiki.commonjs.org/wiki/Modules/Wrappings)规范,此规范指出了一个模块应该如何“包装”,包含以下内容:

  • 全局有一个module变量,用来定义模块
  • 通过module.declare方法来定义一个模块
  • module.declare方法只接收一个参数,那就是模块的factory,次factory可以是函数也可以是对象,如果是对象,那么模块输出就是此对象。
  • 模块的factory函数传入三个参数:require,exports,module,用来引入其他依赖和导出本模块API
  • 如果factory函数最后明确写有return数据(js函数中不写return默认返回undefined),那么return的内容即为模块的输出。

UMD

为了支持一个模块同时兼容AMD和CommonJs规范,适用于 同时支持浏览器端和服务端引用的第三方库,提出了 UMD规范。UMD是一个时代的产物,当各个环境最终实现ES harmony的统一的规范后,它也将退出历史舞台。

// 定义一个模块
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之类的
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    //    方法
    function a(){};    //    私有方法,因为它没被返回 (见下面)
    function b(){};    //    公共方法,因为被返回了
    function c(){};    //    公共方法,因为被返回了

    //    暴露公共方法
    return {
        b: b,
        c: c
    }
}));

CMD

CMD 是 “Common Module Definition”的缩写,意思是通用模块规范。CMD专门用于浏览器端, 模块的加载是异步的 ,模块使用时才会加载执行。在 CMD 规范中,一个模块就是一个文件。规范地址: https://github.com/cmdjs/specification/blob/master/draft/module.md

// 定义一个模块

define(function(require, exports, module) {
   ....
})


//sea.js:
define(function(require, exports, module) {

    var mod_A = require("dep_A");
    var mod_B = require("dep_B");
    var mod_C = require("dep_C");
});

ES2015 Module(es6初版)

ES6是ECMA的为JavaScript制定的第6个版本的标准,标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。ECMAscript 2015 是在2015年6月份发布的ES6的第一个版本。

// file lib/greeting.js 定义一个模块
const helloInLang = {
    en: 'Hello world!',
    es: '¡Hola mundo!',
    ru: 'Привет мир!'
};
// 对外输出
export const greeting = {
    sayHello: function (lang) {
        return helloInLang[lang];
    }
};

// file hello.js 引入一个模块
import { greeting } from "./lib/greeting";
const phrase = greeting.sayHello("en");
document.write(phrase);


webpack(构建工具)

webpack 自己实现了一套模块机制,无论是 CommonJS 模块的 require 语法还是 ES6 模块的 import 语法,都能够被解析并转换成指定环境的可运行代码。随着webpack打包工具的流行,ES6语法广泛手中,后来的开发者对于 AMD CMD的感知越来越少。

CMD 模块定义规范

胖蔡阅读(290)

在 Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。

在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:

define(factory);

define Function

define 是一个全局函数,用来定义模块。

define define(factory)

define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:

define({ "foo": "bar" });

也可以通过字符串定义模板模块:

define('I am a template. My name is {{name}}.');

factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:requireexports 和 module

define(function(require, exports, module) {

// 模块代码

});

define define(id?, deps?, factory)

define 也可以接受两个以上参数。字符串 id 表示模块标识,数组 deps 是模块依赖。比如:

define('hello', ['jquery'], function(require, exports, module) {

  // 模块代码

});

id 和 deps 参数可以省略。省略时,可以通过构建工具自动生成。

注意:带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。

define.cmd Object

一个空对象,可用来判定当前页面是否有 CMD 模块加载器:

if (typeof define === "function" && define.cmd) {
  // 有 Sea.js 等 CMD 模块加载器存在
}

require Function

require 是 factory 函数的第一个参数。

require require(id)

require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。

define(function(require, exports) {

  // 获取模块 a 的接口
  var a = require('./a');

  // 调用模块 a 的方法
  a.doSomething();

});

注意:在开发时,require 的书写需要遵循一些 简单约定

require.async require.async(id, callback?)

require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。

define(function(require, exports, module) {

  // 异步加载一个模块,在加载完成时,执行回调
  require.async('./b', function(b) {
    b.doSomething();
  });

  // 异步加载多个模块,在加载完成时,执行回调
  require.async(['./c', './d'], function(c, d) {
    c.doSomething();
    d.doSomething();
  });

});

注意require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

require.resolve require.resolve(id)

使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。

define(function(require, exports) {

  console.log(require.resolve('./b'));
  // ==> http://example.com/path/to/b.js

});

这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。

exports Object

exports 是一个对象,用来向外提供模块接口。

define(function(require, exports) {

  // 对外提供 foo 属性
  exports.foo = 'bar';

  // 对外提供 doSomething 方法
  exports.doSomething = function() {};

});

除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。

define(function(require) {

  // 通过 return 直接提供接口
  return {
    foo: 'bar',
    doSomething: function() {}
  };

});

如果 return 语句是模块中的唯一代码,还可简化为:

define({
  foo: 'bar',
  doSomething: function() {}
});

上面这种格式特别适合定义 JSONP 模块。

特别注意:下面这种写法是错误的!

define(function(require, exports) {

  // 错误用法!!!
  exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});

正确的写法是用 return 或者给 module.exports 赋值:

define(function(require, exports, module) {

  // 正确写法
  module.exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});

提示exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。

module Object

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

module.id String

模块的唯一标识。

define('id', [], function(require, exports, module) {

  // 模块代码

});

上面代码中,define 的第一个参数就是模块标识。

module.uri String

根据模块系统的路径解析规则得到的模块绝对路径。

define(function(require, exports, module) {

  console.log(module.uri); 
  // ==> http://example.com/path/to/this/file.js

});

一般情况下(没有在 define 中手写 id 参数时),module.id 的值就是 module.uri,两者完全相同。

module.dependencies Array

dependencies 是一个数组,表示当前模块的依赖。

module.exports Object

当前模块对外提供的接口。

传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:

define(function(require, exports, module) {

  // exports 是 module.exports 的一个引用
  console.log(module.exports === exports); // true

  // 重新给 module.exports 赋值
  module.exports = new SomeClass();

  // exports 不再等于 module.exports
  console.log(module.exports === exports); // false

});

注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

// x.js
define(function(require, exports, module) {

  // 错误用法
  setTimeout(function() {
    module.exports = { a: "hello" };
  }, 0);

});

在 y.js 里有调用到上面的 x.js:

// y.js
define(function(require, exports, module) {

  var x = require('./x');

  // 无法立刻得到模块 x 的属性 a
  console.log(x.a); // undefined

});

小结

这就是 CMD 模块定义规范的所有内容。经常使用的 API 只有 definerequirerequire.asyncexportsmodule.exports 这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。

与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行

js的数据类型之Symbol

方玉新阅读(246)

一、出现原因

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。为了解决这一问题,在ES6中便新增了一种新的原始数据类型Symbol,它表示独一无二的值。前面我们说过JavaScript的六种数据类型分别是:

undefined, null, Boolean, String, Number, Object

加上现在说的Symbol,现在有七种了。Symbol实际上是一种唯一的标识符,可以作为对象的唯一属性名,这样就不会被覆盖了。

Symbol 值通过Symbol函数生成,于是对象的属性名便有两种类型,一种是原来的字符串,另外就是现在说的Symbol类型。

let a = Symbol()typeof a // symbolconsole.log(a) // Symbol()let b = Symbol('b')typeof b // "symbol"console.log(b) // Symbol(b)

注意:Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。实际上它是一种类似于字符串的数据类型。

二、特性

1、相同参数的Symbol函数返回的值是不相同的

let a1 = Symbol('a')let a2 = Symbol('a')console.log(a1 == a2,a1,a2) // false Symbol(a) Symbol(a)

这是因为Symbol函数的参数只是对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

2、Symbol值不能与其他类型的数据进行运算,但是Symbol值可以显式转为字符串,Symbol值也可以转为布尔值

Symbol() + 1 // Uncaught TypeError: Cannot convert a Symbol value to a number'this is ' + Symbol('a') // Uncaught TypeError: Cannot convert a Symbol value to a string
let str = Symbol('str')console.log(String(str)) // "Symbol(str)"Boolean(str) // true

3、Symbol的值作为对象的属性名,由于Symbol值的唯一性,因此不会覆盖属性名

let attr = Symbol()// 第一种写法let obj = {}obj[attr] = 'Hello!'console.log(obj,obj[attr]) // {Symbol(): "Hello!"}// 第二种写法let obj = { [attr] : 'hello!' }console.log(obj,obj[attr]) // {Symbol(): "Hello!"}// 第三种写法let obj = {}Object.defineProperty(obj, attr, {value: 'hello!'})console.log(obj,obj[attr]) // {Symbol(): "Hello!"}

4、Symbol值作为对象属性名时,不能用点运算符

let attr = Symbol()const obj = {}obj.attr = 'hello!'console.log(obj[attr], obj['attr']) // undefined "hello!"

因为点运算符后面总是字符串,所以不会读取attr作为标识名所指代的那个值,导致obj 的属性名实际上是一个字符串,而不是一个 Symbol 值。所以在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

let attr = Symbol()let obj = {  [attr]: function(param){    console.log(param)  }}obj[attr](1234)

5、Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的

let greet = {}greet.levels = {  DEBUG: Symbol('debug'),  INFO: Symbol('info'),  WARN: Symbol('warn')  }console.log(greet.levels.DEBUG, 'debug message'); // Symbol(debug) "debug message"console.log(greet.levels.INFO, 'info message'); // Symbol(info) "info message"

注意Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

三、Symbol 作为属性名的遍历

1、Object.getOwnPropertySymbols() 遍历属性名

当symbol作为属性名时,遍历属性名时,不能从for...infor...of循环中取出,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

此时需要用Object.getOwnPropertySymbols()方法获取指定对象的所有 Symbol 属性名,该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

const attr1 = Symbol('attr1')const attr2 = Symbol('attr2')let obj = {  [attr1]:'hello1',  [attr2]:'hello2',}const res = Object.getOwnPropertySymbols(obj)console.log(res) // [Symbol(attr1), Symbol(attr2)]

2、Reflect.ownKeys() 返回属性名 

Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

let obj = {  a:1,  b:[1,2],  [Symbol('c')]:'str'}Reflect.ownKeys(obj) // ["a", "b", Symbol(c)]

四、Symbol 相关函数使用

1、Symbol.for(),Sysmbol.keyFor()

当我们需要使用相同的Sysmbol值时,Symbol.for()就可以生成相同的值。它需要传一个字符串参数,然后他会先搜索是否存在以该参数作为名称的Symbol的值,如果有则返回这个Symbol的值,否则创建该字符串为名称的Symbol值并将其注册到全局。

let obj1 = Symbol.for('a')let obj2 = Symbol.for('a')console.log(obj1 === obj2) // true

Symbol.for()Symbol()

它们都会生成新的Symbol值,区别是Symbol.for()被登记在全局环境中供搜索而Symbol()则不会。

console.log(Symbol.for('a') === Symbol.for('a')) // trueconsole.log(Symbol('a') == Symbol('a')) // false

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key

let obj1 = Symbol.for('a')console.log(Symbol.keyFor(obj1)) // alet obj2 = Symbol('a')console.log(Symbol.keyFor(obj2)) // undefined

Symbol.for()为Symbol值登记的名字,是全局环境的,不管有没有在全局环境运行。

使用Browserify 实现JS的模块化加载

胖蔡阅读(217)

Broswerify 是一个前端管理依赖的工具,通过它可以在浏览器环境下像nodejs一样遵循commonjs规范的模块化编程。

为什么要使用Browserify

浏览器没有定义require方法,但是Node.js有。使用Browserify可以编写使用require的代码,就像在Node中使用它一样。

原理

Browserify从entry着手,对源代码进行抽象语法树分析,从而获得整个项目的依赖关系图,最后将整个项目打包成一个JavaScript文件。特点如下:

  • 浏览器端的前端打包工具
  • 主要用于在浏览器中使用 npm 包,最终会转换为 commonJS (require) 类似方式,在浏览器使用
  • 方便模块细分,每个模块自成,通过 require 引用其他模块
  • 基于流 Stream

实现

  • 创建add.js文件
module.exports.add = function(a,b) {
    return a + b;
}

  • 创建reduce.js文件
module.exports.reduce = function(a,b){
    return a-b;
}
  • 创建 index.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Browserify 测试</title>
</head>

<body align="center">
    <h3>Browserify 测试</h3>
    <form style="text-align: left;margin-left: 40%;">
        <input name="a"  type="input" placeholder="请输入第一个操作数"/>
        <input name="b"  type="input" placeholder="请输入第二个操作数"/>
        <div style="margin-top: 20px;">合计:<span id="result"></span></div>
        <div style="margin-top: 20px;">
            <button id="add" type="button">加法</button>
            <button id="reduce" type="button" style="margin-left: 30px;">减法</button>
        </div>
    </form>
    <script src="./bundle.js" ></script> <!--打包生成文件 -->
</body>
</html>
  • 创建main.js文件
var { add }  = require('./add');
var { reduce } = require('./reduce');

// 加法
document.getElementById('add').onclick =  function () {
    const a = document.getElementsByName('a')[0].value;
    const b = document.getElementsByName('b')[0].value;
    document.getElementById('result').innerHTML = add(a,b);
}

// 减法
document.getElementById('reduce').onclick =  function () {
    const a = document.getElementsByName('a')[0].value;
    const b = document.getElementsByName('b')[0].value;
    document.getElementById('result').innerHTML = reduce(a,b);
}

  • 生成 bundle.js文件
$ npm install -g browserify
$ browserify  main.js > bundle.js

效果

'); })();