{:modules
{:vendor {:output-to "..."
:entries '#{cljsjs.react reagent.* re-frame.*}}
:main {:output-to "..."
:entries '#{myapp.core}
:depends-on [:vendor]}}
2017 年 7 月 10 日
David Nolen
这是抢先预览系列的第一篇文章。
随着客户端应用程序规模的增长,优化加载逻辑屏幕的时间变得越来越重要。网络请求应该尽量减少,同时加载的代码应该限制在绝对必要的范围,以生成一个正常工作的屏幕。虽然像 Webpack 这样的工具在 JavaScript 主流中普及了这种优化技术,但 Google Closure Compiler 和 Library 在 Google Closure Modules 的形式下多年来一直支持相同的优化策略。
Google Closure Modules 还提供了一些相对于 Webpack 或 Rollup 这样的工具的独特优势,我们将在本文的技术部分进行介绍。简而言之,我们以最佳方式将所有源代码分配给模块,然后 Google Closure Compiler 使用死代码消除(树形抖动)和跨模块代码移动来生成真正最佳的拆分。
虽然 ClojureScript 一直以来都提供了与该功能的基本集成,但下一个版本将提供对代码拆分和这些拆分的异步加载的极大增强和全面支持。
如果您熟悉 Webpack 术语,在以下描述中请注意,此处的 **模块** 指的是 **代码拆分** 或 **块**。
**入口点** 指的是表示应用程序逻辑入口点的源文件(登录、用户、管理员等)。
不再需要手动优化源代码的模块分配。所有源代码将根据应用程序的依赖关系图以最佳方式分配给模块。如果您有一个包含大量手动分配的模块,您现在应该删除这些分配。如果您使用的是命名空间通配符匹配,现在也不再需要它了。有关我们如何将输入分配给特定模块的详细信息,请参阅下面的技术描述。
具体来说,以下现在是一种反模式
{:modules
{:vendor {:output-to "..."
:entries '#{cljsjs.react reagent.* re-frame.*}}
:main {:output-to "..."
:entries '#{myapp.core}
:depends-on [:vendor]}}
以前您必须手动将源代码(在本例中为 re-frame
)及其依赖项固定到模块。现在只需要
{:modules
{:vendor {:output-to "..."
:entries '#{re-frame.core}
:main {:output-to "..."
:entries '#{myapp.core}
:depends-on [:vendor]}}
另一个重大增强是,:modules
现在在所有优化设置下都能正常工作。通过在所有编译模式下统一 :modules
行为,我们消除了开发和生产之间构建配置的一些偶然复杂性。
模块拆分的异步加载现在通过引入 cljs.loader
命名空间进行了标准化。如果应用程序中的任何入口点需要由于用户操作而调用另一个模块的加载,您现在可以使用 cljs.loader
。
cljs.loader
提供了一个共享的 Google Closure ModuleManager 单例,它会自动初始化为您的 :modules
图,而不管优化级别如何。
以下是一个简单的 cljs.loader
功能示例
(ns views.user
(:require [cljs.loader :as loader]
[goog.dom :as gdom]
[goog.events :as events])
(:import [goog.events EventType]))
(events/listen (gdom/getElement "admin") EventType.CLICK
(fn [e]
(loader/load :admin
(fn [e]
((resolve 'views.admin/init!))))))
(loader/set-loaded! :user)
请注意,此示例显示了如何在不使编译器抱怨此代码拆分中不存在的功能的情况下跨模块边界调用。这得益于最近将静态 resolve
包含到标准库中。
有关增强 :modules
功能的完整演练,请参阅新指南。
以下重点介绍了增强模块功能的一些有趣的技术细节。
本节简要描述了用于自动将每个源文件分配给模块的算法。
假设一个简化的模块描述,例如
{:modules {:module-a {:entries '#{foo.core}}
:module-b {:entries '#{bar.core}}}
这将转换为包含隐式基本模块 :cljs-base
的模块描述。
{:modules {:cljs-base {:entries []}
:module-a {:entries '#{foo.core}
:depends-on [:cljs-base]}
:module-b {:entries '#{bar.core}
:depends-on [:cljs-base]}}
然后我们将计算图中每个模块的深度
{:modules {:cljs-base {:entries [] :depth 0}
:module-a {:entries '#{foo.core} :depth 1
:depends-on [:cljs-base]}
:module-b {:entries '#{bar.core} :depth 1
:depends-on [:cljs-base]}}
然后,我们使用它来计算从所有依赖的输入到一组可能的模块分配的映射。例如,我们找到 foo.core
的所有依赖项,并假设它们将进入 :module-a
- 甚至 cljs.core
,标准库。
但当然 :module-b
也会将 cljs.core
分配给自己。所以 cljs.core
模块分配为 [:module-a :module-b]
。但是我们只能选择一个。为了选择,我们首先找到所有公共父模块。找到后,我们选择具有最大 :depth
值的模块。
最后,任何孤儿都将被分配给 :cljs-base
。
熟悉 Webpack 的读者会注意到,这种方法将拆分和拆分加载视为两个独立的关注点。因此,拆分定义不需要编辑源代码或引入额外的插件。
虽然 Google 在 2010 年出版的 Closure: The Definitive Guide 中记录了这些功能,但我们认为它们仍然代表了最先进的技术。请在下一个版本中尝试这些增强功能!