RequireJS影响嵌入式jquery-ui小部件

时间:2017-10-18 12:59:58

标签: javascript jquery jquery-ui requirejs

我有一个jQuery小部件是合作伙伴试图嵌入的。我们遇到的问题是合作伙伴正在使用requireJS及其影响小部件。

小部件是匿名函数,需要jquery-ui。在调试之后,我们发现在noConflict调用之后将删除jQuery UI。这是小部件的代码。

(function () {

    // Localize jQuery variable
    var jQueryWidget;

    /******** Load jQuery if not present *********/
    if (window.jQuery === undefined || window.jQuery.fn.jquery !== '3.2.1') {
        var script_tag = document.createElement('script');
        script_tag.setAttribute("type", "text/javascript");
        script_tag.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js");
        script_tag.onload = scriptLoadHandler;
        script_tag.onreadystatechange = function () { // Same thing but for IE
            if (this.readyState == 'complete' || this.readyState == 'loaded') {
                scriptLoadHandler();
            }
        };
        (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
    } else {
        loadJQueryUi();
    }

    function scriptLoadHandler() {
        loadJQueryUi();    
    }

    function loadJQueryUi() {
    /******* Load UI *******/
        jQuery.getScript('https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js', function () {
          jQueryWidget = jQuery.noConflict(true);
          setHandlers(jQueryWidget);
        });


        /******* Load UI CSS *******/
        var css_link = jQuery("<link>", {
            rel: "stylesheet",
            type: "text/css",
            href: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
        });
        css_link.appendTo('head');
    }

    function setHandlers($) {
        $(document).on('focus', '#start-date, #end-date', function(){

      $('#start-date').datepicker({
        dateFormat: "M dd, yy",
        minDate: 'D',
        numberOfMonths: 1,
      });

            $('#end-date').datepicker({
                dateFormat: "M dd, yy",
                minDate:'+1D',
                numberOfMonths:1,
            });
    }
})();

使用chrome调试器,我们可以看到,当调用getScript时,它正确地将jquery-ui添加到已加载的版本中。在我们调用noConflict之后,它恢复了之前的jQuery,但版本不再具有jQueryUI。

在没有requireJS的情况下在其他网站上测试窗口小部件正常工作。

有没有人遇到过这个?不幸的是我们之前没有使用过RequireJS,但是无法理解为什么它会影响匿名函数。

任何帮助都会非常感激。

2 个答案:

答案 0 :(得分:2)

问题是你想要做的是不安全的。结合起来有两个因素对你不利:

  1. 脚本以异步方式加载。您唯一控制的是窗口小部件加载jQuery和jQueryUI的相对顺序。但是,窗口小部件运行的页面也会加载自己的jQuery版本。 您的代码无法强制加载合作伙伴代码加载的脚本的顺序。

  2. jQuery不是一个表现良好的AMD模块。一个行为良好的AMD模块调用define来获取它的依赖关系,它不会泄漏到全局空间。不幸的是,jQuery确实将$jQuery泄漏到全局空间中。

  3. 结合这两个因素,您将面临竞争条件,具体取决于加载两个版本的jQuery的顺序:通常无法知道哪个版本的jQuery是全局符号$和{{1}指的是。考虑一下你的代码:

    jQuery

    您无法知道jQuery.getScript('https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js', function () { jQueryWidget = jQuery.noConflict(true); setHandlers(jQueryWidget); }); 是指您要求加载的版本还是合作伙伴代码要加载的版本。 jQuery唯一保证的是,在加载脚本之后,回调将被称为,但阻止其他脚本在{{ {1}}加载和调用回调的时间。浏览器可以完全免费加载您提供给.getScript的脚本,加载通过RequireJS请求的其他脚本,然后调用您的回调。

    如果您希望自己的窗口小部件可以放入页面而不必更改任何现有代码,那么就没有简单的修复方法。您不仅可以更改问题中显示的逻辑,还可以将RequireJS添加到窗口小部件中。 RequireJS不能自己修复此问题。与其他答案所暗示的相反,RequireJS的.getScript配置选项不是修复。如果没有脚本尝试通过全局.getScriptcontext访问jQuery,那将是一个修复,但是有几十个jQuery插件可以做到这一点。您无法确保合作伙伴代码不使用它们。

    请注意似乎的修复建议以解决问题。你可以尝试一个修复,它似乎工作,你认为问题已经解决,但实际上问题并没有表现出来,因为,这是一个竞争条件。一切都很好,直到一个月后,另一个合作伙伴加载你的小部件和繁荣:他们的页面创建了正确的条件,导致事情加载到一个破坏你的代码的顺序。

    还有一个额外的并发症,你可能还没有遇到,但必然会不时发生。 (再次,你正在处理竞争条件,所以......)你的代码是通过$元素加载jQuery和jQuery UI。但是,它们都会检查jQuery是否可用,如果可用,则会调用script。这可能会导致各种问题,具体取决于所有事情发生的顺序,但一个可能的问题是,如果在加载窗口小部件之前存在RequireJS,jQuery UI将从define元素调用define,这将导致一个mismatched anonymous define error。 (jQuery有一个不同的问题,它更复杂,不值得进入。)

    我可以看到让您的窗口小部件加载而不受合作伙伴代码干扰的唯一方法,并且无需合作伙伴更改其代码将使用类似Webpack的内容将代码构建到单个捆绑包中{{ 1}}应该被强制在你的包中伪造,以便不会触发任何测试define存在的代码。 (请参阅import-loader,可以将其用于此。)如果您将代码作为单个包加载,那么它可以以同步方式初始化自己,并且您可以确保script和{{ 1}}请参阅您的捆绑包中包含的jQuery。

    如果您要遵循我的建议,这是一个很好的例子,它充分利用了Webpack,包括正确的缩小,并消除了代码中的一些工件,这些工具不再需要这种方法(例如IIFE,和你有的一些功能)。它可以通过保存文件在本地运行,运行:

    1. define
    2. define
    3. $
    4. 当我第一次写下我的解释但我现在注意到的时候,我没有意识到这一点。当你用Webpack包装你的代码时没有必要调用jQuery,因为当它被Webpack包装时,jQuery会检测到一个带有DOM的CommonJS环境,并在内部打开一个npm install webpack jquery jquery-ui imports-loader lite-server标志,防止泄漏到全球空间。

      ./node_modules/.bin/webpack

      ./node_modules/.bin/lite-server

      您的小部件为noConflict

      noGlobal

      webpack.conf.js

      const webpack = require('webpack');
      module.exports = {
          entry: {
              main: "./widget.js",
              "main.min": "./widget.js",
          },
          module: {
              rules: [{
                  test: /widget\.js$/,
                  loader: "imports-loader?define=>false",
              }],
          },
          // Check the options for this and use what suits you best.
          devtool: "source-map",
          output: {
              path: __dirname + "/build",
              filename: "[name].js",
              sourceMapFilename: "[name].map.js",
          },
          plugins: [
              new webpack.optimize.UglifyJsPlugin({
                  sourceMap: true,
                  include: /\.min\.js$/,
              }),
          ],
      };
      

答案 1 :(得分:-1)

我认为问题出在jQueryWidget = jQuery.noConflict(true);

true表示从全局范围中删除所有jQuery变量。

  

jQuery.noConflict([removeAll])   移除所有   类型:布尔值   一个布尔值,指示是否从全局范围中删除所有jQuery变量(包括jQuery本身)。

[noconflict] [1]

我们可以尝试删除真正的布尔参数,让我们知道它是否有帮助。

  

更新2:     以下方法不应要求任何合作伙伴代码更改

<!DOCTYPE html>
        <html>

        <head>
            <title></title>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.js"></script>
            <script type="text/javascript">
            (function() {

                // Localize jQuery variable
                var jQueryWidget;
                /*
                *
                *
                *
                    This is plugin's require config. Only used by plugin and
                    will not affect partner config.
                *
                *
                */
                var requireForPlugin = require.config({
                    context: "pluginversion",
                    paths: {
                        "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min",

                        "jquery.ui.widget": "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min"

                    }
                });
                requireForPlugin(["require", "jquery", "jquery.ui.widget"], function() {
                    /******** Load jQuery if not present *********/
                    if (window.jQuery === undefined || window.jQuery.fn.jquery !== '3.2.1') {
                        scriptLoadHandler();
                    } else {
                        loadJQueryUi();
                    }

                    function scriptLoadHandler() {
                        loadJQueryUi();
                    }

                    function loadJQueryUi() {
                        jQueryWidget = jQuery.noConflict(true);
                        setHandlers(jQueryWidget);
                        var css_link = jQueryWidget("<link>", {
                            rel: "stylesheet",
                            type: "text/css",
                            href: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
                        });
                        css_link.appendTo('head');
                    }

                    function setHandlers($) {
                        $('#end-date').on('click', function() {
                            alert('JQUERY--' + $().jquery);
                            alert('JQUERY UI--' + $.ui.version);
                            $('#end-date').datepicker({
                                dateFormat: "M dd, yy",
                                minDate: '+1D',
                                numberOfMonths: 1,
                            });
                        });
                    }
                });
            })();
            </script>
            <script>
            //SAMPLE PARTNER APPLICATION CODE:

            /*
            *
            *
            *
                This is partner's require config
                which uses diffrent version of jquery ui and
                jquery
            *
            *
            */
            require.config({
                paths: {
                    "jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min",

                    "jquery.ui.widget": "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui"

                }
            });
            require(["jquery", "jquery.ui.widget"], function() {
                $('#btn').on('click', function() {
                    alert('JQUERY--' + $().jquery);
                    alert('JQUERY UI--' + $.ui.version);
                });
            })
            </script>
        </head>

        <body>
            <div><span>FOCUS</span>
                <input type="text" name="" id="end-date" />
            </div>
            <button id="btn" style="width: 100px; height:50px; margin:10px">click me</button>
        </body>

        </html>

我修改了插件代码,因此它使用自己的jquery和jquery ui版本(我在这里使用了requirejs)。

同样对于演示,我添加了一个示例合作伙伴脚本,该脚本在按钮点击时发出警报。你可以看到,无需修改合作伙伴代码和requirejs配置,插件现在可以正常工作。

插件和合作伙伴代码都有独立的jquery和jquery ui版本。

希望这有帮助。

参考Using Multiple Versions of jQuery with Require.jshttp://requirejs.org/docs/api.html#multiversion

UPDATE3:使用webpack并导入加载程序 我们可以使用webpack捆绑插件js代码,在这种情况下,插件将拥有自己的jquery版本,我们必须改变插件的构建方式。

使用npm安装webpack,jquery,import loader和jquery-ui并构建它,下面是示例代码:

main.js 使用import loader将define定义为false

require('imports-loader?define=>false!./app.js');

app.js ,其中包含插件代码,并添加了所需的依赖项

 (function() {

     var $ = require('jquery');
     require('jquery-ui');
     require('jquery-ui/ui/widgets/datepicker.js');

     function scriptLoadHandler() {
         loadJQueryUi();
     }

     $(document).ready(function() {
         scriptLoadHandler();
     });

     function loadJQueryUi() {
         setHandlers();
         var css_link = $("<link>", {
             rel: "stylesheet",
             type: "text/css",
             href: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
         });
         css_link.appendTo('head');
     }

     function setHandlers() {
         $('#end-date').on('click', function() {
             alert('JQUERY--' + $().jquery);
             alert('JQUERY UI--' + $.ui.version);
             $('#end-date').datepicker({
                 dateFormat: "M dd, yy",
                 minDate: '+1D',
                 numberOfMonths: 1,
             });
         });
     }
 })();

<强> webpack.config.js

  var webpack = require('webpack');

  module.exports = {
  entry: "./main.js",
  output: {
  filename: "main.min.js"
     }
  };

<强> sample.html

<!DOCTYPE html>
<html>

<head>
    <title></title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.js"></script> 
    <script src="main.min.js"></script> 
    <script>
    //SAMPLE PARTNER APPLICATION CODE:

    /*
    *
    *
    *
        This is partner's require config
        which uses diffrent version of jquery ui and
        jquery
    *
    *
    */
    require.config({
        paths: {
            "jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min",

            "jquery.ui.widget": "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui"

        }
    });
    require(["jquery", "jquery.ui.widget"], function() {
        $('#btn').on('click', function() {
            alert('JQUERY--' + $().jquery);
            alert('JQUERY UI--' + $.ui.version);
        });
    })
    </script>
</head>

<body>
    <div><span>FOCUS</span>
        <input type="text" name="" id="end-date" />
    </div>
    <button id="btn" style="width: 100px;"></button>
</body>

</html>

执行webpack后,它将生成一个main.min.js文件,该文件包含在sample.html文件中