GWT代码拆分同步请求

GWT code splitting synchronous request

我在 GWT 中使用代码拆分来减少初始 JavaScript 的大小。 当我的应用程序初始化时,我想预取我的代码的其他(更大)部分,如文档中所述(www.gwtproject。org/doc/latest/DevGuideCodeSplitting。html)。

private void doSth(final boolean prefetch) {
    GWT.runAsync(new RunAsyncCallback() {
        public void onFailure(Throwable caught) {
            Log.error("Loading the code failed!");
        }

        public void onSuccess() {
            if(prefetch)
                return; //do nothing. just a prefetch
            //here is the loaded code
        }
    });
}

但我无法识别性能改进。当我分析浏览器日志时,我发现加载 JavaScript 的请求未标记为 XHR。 GWT是否同步加载分割点的代码?

性能改进是在最初下载的代码中,假设没有其他引用该代码。如果有其他任何东西可以完成 //here is the loaded code 的工作,那么将很少或没有代码可以分解为单独下载的 JS 文件。

可以通过多种方式禁用此功能,包括使用开发模式或设置编译器标志以跳过此过程。在这种情况下,是的,分割点是同步运行的,因为等待是没有意义的。此外,文件加载一次后,下次在同一页面加载中调用代码时无需加载。

如果您的服务器设置为正确缓存,那么在第一次访问后节省的费用会更少,因为无需下载 - 您只节省了将该代码解析到浏览器的 JS VM 中所花费的时间。

但除此之外,我们还需要更多信息。


这里有一个快速演示,展示了如何编写拆分点并添加更多内容,并让您使用浏览器注意到拆分点代码是如何单独引入的。

public class SampleEntryPoint implements EntryPoint {
  public void onModuleLoad() {
    Label label = new Label("Hello, World!");
    label.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        GWT.runAsync(new RunAsyncCallback() {
          public void onFailure(Throwable var1) {/*ignore*/}
          public void onSuccess() {
            Window.alert("Clicked, and loaded in split point!");
          }
        });
      }
    });
    RootPanel.get().add(label);
  }
}

代码和示例: https://viola.colinalworth.com/proj/755e224e7f48a047703d44eb6903d926/project/client/SampleEntryPoint.java

独立示例: https://viola.colinalworth.com:444/compiled/755e224e7f48a047703d44eb6903f76c/

加载此页面时,会加载 nocache 文件,初始下载也是如此(通过 Chrome 检查器的网络选项卡可以看到):

然后,当您单击 Label 小部件时,将触发 onClick,从而触发 runAsync 并下载额外的拆分点(加上 "leftover" 片段):

将这两个新条目添加到“网络”选项卡后,您会看到出现警告消息。随后的点击不会导致这种轻微的延迟,也不会强制再次下载这个额外的 JS。

另请注意,这些不是作为 AJAX/XHR 调用加载的,而是作为要添加到页面的脚本标记加载的。单击 Initiator 列中的详细信息(未显示)会导致此结果(为了便于阅读而设置格式):

function fb(a) {
    var b, c, d;
    d = (bb(), window);
    b = d.document;
    c = b.createElement('script');
    (!!a.a || a.b) && cb(c, a.a, a.b);
    eb(c, a.c);
    (b.head || b.getElementsByTagName('head')[0]).appendChild(c);
    return c
}

通过混淆代码,我们看到创建了一个 <script> 标记,并附加到页面的 <head>


深入挖掘,我们可以发现 AsyncFragmentLoader.LoadingStrategy 接口描述了如何获取此片段,并且 com/google/gwt/core/AsyncFragmentLoader.gwt.xml 默认将其连接到 XhrLoadingStrategy。但是,xsxsiframe 链接器都将此更改为 CrossSiteLoadingStrategyScriptTagLoadingStrategy。对于最新版本的 GWT(您没有指定,所以我假设您使用的是最新版本),xsiframe 链接器是默认的。来自 Core.gwt.xml:

<add-linker name="xsiframe" />

我们可以通过切换到旧链接器或仅替换策略来自定义它。请注意,切换 XHR 策略将阻止跨域加载正常工作(例如 SuperDevMode),因此请小心。

就像 AsyncFragmentLoader.gwt.xml 将接口连接到 XhrLoadingStrategy,并且 CrossSiteIframeLinker.gwt.xml 将其更改为 ScriptTagLoadingStrategy,我们可以将其改回。我们创建一个规则,将LoadingStrategy替换为XhrLoadingStrategy,并在我们的GWT继承我们的.gwt.xml文件中的语句后列出:

<replace-with class="com.google.gwt.core.client.impl.XhrLoadingStrategy">
    <when-type-is class="com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy" />
</replace-with>

这是作为 std 链接器 (com.google.gwt.core.linker.IFrameLinker) 一部分的旧默认值,尽管不再鼓励这样做,并且可能会在以后的版本中删除。