`
hax
  • 浏览: 950823 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

论ES6模块系统的静态解析

    博客分类:
  • JS
阅读更多
本文是Dave Herman的《Static module resolution》一文的编译。Dave Herman是TC39的成员,ES6 module系统的champion。【ES6 spec太大了,所以分成许多可相对独立的特性集合,分别交给一个或几个主导人负责,TC39委员会则会定期开会进行审阅和讨论。主导人就称之为champion。】


在纯JS环境下已经有多种模块系统。比如CommonJS。所谓纯JS系统,就是不依赖其他机制如预处理之类的。纯JS系统中的模块都是一个个对象。客户代码导入模块所导出的定义,实际上是查找module对象上的属性:

var { stat, exists, readFile } = require('fs');


ES6模块系统则相反,模块不是对象,而是声明式的代码集合。从模块导入定义也是声明式的:

import { stat, exists, readFile } from 'fs';


这个import是在编译时resolve的——即在脚本开始执行之前。事实上,各个模块之间的依赖关系图所涉及的所有imports和exports都是在执行之前resolve好了。当然,我们也有lazy loading或按需加载的需求,即在运行时才进行模块加载。对此,ES6也有异步的模块动态加载API。不过本文只讨论声明式模块依赖关系图的解析。

NodeJS的作者认为我们应该走渐进的、改良式的道路,认为ES6的模块系统应该更接近今天已经存在着的模块系统。我也相当赞同“pave the cowpaths”哲学(遵循事实标准),并常以此立论,但是必须注意到,现有JS模块系统的作者们从未有过从语言层面做修改的可能性,而我们现在却有机会改变JS,选择在纯动态系统中没可能走的道路,包括:

快速查找
静态import(无论是通过import还是如m.foo的引用)可以编译为如同简单的变量引用一样。在动态模块系统中,像m.foo这样的显式用引(dereference)会得到一个对象引用,通常需要PIC才能优化(Polymorphic Inline Caching,多态内联缓存,JavaScript引擎在执行时动态修改JIT代码的高级优化技术)。如果是复制到局部变量,相对来说会较容易进行优化。但是对于静态模块来说,总是早期绑定,也就是始终和变量引用一样高效。这使得模块化的程序能运行更快,避免了因为模块化导致额外性能成本。

早期变量检查
依我的经验,在脚本执行前若能对变量引用——包括imports和exports——进行检查,非常有助于确保程序顶层的基础结构是健全的。JavaScript基本上是静态作用域的,因此可以进行静态作用域检查,这也是唯一可以做的检查。James Burke认为这只是shallow type checking(浅类型检查,而不是强类型检查),不够有用。但我在其他语言的经验表明正相反——这超级有用!变量检查是一个最佳平衡点,你可以写出富有表达力的动态程序,同时又能捕捉到那些真的很常见的错误。如Anton Kovalyov指出的,报告未绑定的变量是JSHint的最常用特性,如果不必借助额外的lint工具就能捕捉这些bug那就再好不过了。

循环依赖
允许模块间循环依赖是非常重要的。现实情况是编程中可能出现相互间的递归调用——有时你甚至都没注意到。如果你将程序拆分模块后,由于不能处理循环依赖结果系统挂了,那最简单的workaround就是继续把所有东西都堆到一个大模块中。这肯定有问题。无论如何,模块系统不应该阻止程序员拆分程序,不应该挫伤程序员模块化的积极性。

不是说动态系统就不可能支持循环依赖,但是我觉得在那些提案中看起来都像是事后补丁。ES6的静态模块系统则仔细的考虑了循环依赖问题。声明式的模块让你可以在执行任何代码前预初始化更多的模块结构,这样如果引用尚未赋值的export,能得到更好的错误信息。例如,一个let绑定会扔出异常——如果你在它被赋值之前就引用它的话——你可以得到清晰的错误信息。而一个动态模块对象上的属性如果还未赋值就被引用,得到的是undefined,最终错误可能发生在客户代码中,必须跟踪这个错误直到源头——这比异常要难调试太多了。

兼容未来的macro特性
我非常期待JavaScript未来能让程序员可以发展他们自己的定制语法扩展,而不必等待TC39。今天,人们自个儿写编译器来弄新语法。但是这个极难,而且你不能在同一个源文件里使用不同编译器提供的不同语法特性。
有了macro,你就可以实现,比如说一个新的cond语法,来取代连续的? :条件分支,并可以通过库的方式共享之:

import cond from 'cond.js';
...
var type = cond {
    case (x === null): "null",
    case Array.isArray(x): "array",
    case (typeof x === "object"): "object",
    default: typeof x
};


cond这个macro会在程序运行前进行预处理,将这段代码转换为连续的条件分支。而纯动态模块是无法实现预处理的:

var cond = require('cond.js');
...
// impossible to preprocess because we haven't evaluated the require!
var type = cond { /* etc */ };


兼容未来的类型系统
在悲剧的ES4时代我就加入了TC39,当时委员会在搞一个可选的类型系统。这系统基础不全最终废弃。其中一个重要缺失就是模块系统,通过模块系统可以将代码划定边界并说“这部分需要类型检查”。否则你永远不知道是否有更多后续代码会影响类型检查。

为什么要有类型系统?一个原因是:JS很快且越来越快,但是也更难准确预测性能。通过类似LLJS的试验性系统,我在Mozilla的团队使用带有类型的JS方言进行预编译,生成相当独特的为当前JIT优化的JS代码。如果你可以直接用带类型系统的JS方言写出高性能核心,现代编译器可以做得更好而不用如此曲折。

通过声明性的解析,你可以导入和导出带有类型信息的定义,并可进行编译时检查。动态导入不可能进行静态检查。

跨语言的模块性
一些人不care或者不想要像macro或类型这样的特性。但是JavaScript必须适应许多不同的程序员的各种不同的开发实践和需求。其中一种方式是让人们使用他们自己的语言,并编译为JavaScript。所以即使未来的ECMAScript标准没有macro和类型,若你可以使用静态类型或带有macro的JS方言并编译为浏览器可执行的JS,也是相当好的。实际上人们已经这样干了,比如用Closure compiler的类型检查、Roy语言、ClojureScript等。静态模块系统可以更一致更直接的兼容更多的语言。

成本和收益
以上是一些我看到的声明性模块解析的收益。Isaac Schlueter(NodeJS的作者)说import语法无甚意义。这是不公正和错误的。它是有意义的。我也不认为声明性的import语法会给ES6和未来的JS版本增加很高的成本。



0
0
分享到:
评论

相关推荐

    es6-module-transpiler-npm-resolver:使用`jsnext 从 NPM 模块到解析器的 ES6 模块转译器扩展

    转译过程的一部分是验证和解析过程,这是 ES6 模块的好处之一,如果您尝试导入的模块不可用,或者您引用的命名导出不可用,则报告静态错误存在。 默认情况下,转译器实现了一个相对路径解析过程,这意味着只有公共...

    view:前端模块化框架-view:轻便、模块化、编写灵活、路由模式、MVVM模式

    2020-11-20更新####更新了解析稳定性;让文件目录更合理;现在可以将整个web灵活部署于多个对象存储了。...2. 单页面,模块化,静态化;3. 思维来源于php的模板渲染,同时为了摆脱代码中夹杂php代码;4. 一些安全设置

    webpack-test

    它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。 Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。这样,...

    webpack-build-system:固执己见的使用Webpack的构建系统

    它需要具有依赖性JavaScript模块(即其他JavaScript文件,CSS,图像等),并为浏览器生成代表这些模块的静态资产。 它与相似,有一些额外的功能,例如 。 装载机 -CSS并使用“使用”中的值将供应商前缀添加到CSS...

    react-admin:我的React管理员

    企业级的电商管理系统功能模块划分用户模块登录退出用户列表商品模块商品列表商品详情添加/修改商品品类模块品类列表添加品类修改品类名称订单模块订单列表订单详情发货管理后台功能拆分商品管理:添加/编辑商品,...

    projectTemplate:用于创建Web应用程序的项目模板。 它具有webpack和gulp

    Gulp + Webpack + ES6 + Vue或其他Gulp :流式构建系统Webpack :一个打包工具能很好的解析模块依赖并生成相关的静态资源。与生俱来的支持Common Js,AMD,ES6……,它视所有前端资源为模块;同时同时还ES6 :JS的...

    eslint-plugin-import:ESLint插件,其规则有助于验证正确的导入

    规则静态分析确保导入指向可以解析的文件/模块。 ( ) 确保命名导入对应于远程文件中的命名导出。 ( ) 给定默认导入,确保存在默认导出。 ( ) 确保导入的名称空间在被取消引用时包含已取消引用的属性。 ( ) ...

    eslint-plugin-absolute-import:强制在导入中使用绝对路径

    eslint插件绝对导入 该插件旨在支持lints of ES2015 +(ES6 +)导入/导出语法,并防止文件路径和导入名称拼写错误的问题。 ES2015 +静态模块语法打算提供的所有优点,已在您的编辑器中进行了标记。规则静态分析禁止...

    jst-chat:JavaScript技术作业分配

    Express(正文解析器,会话,路由器,静态,视图) 玉 护照(本地策略) 套接字 客户: 引导程序 AngularJS 套接字 jQuery的 CryptoJS / SHA-256 ES6(类,箭头功能) 模块(浏览器化) 其他: VCS:git...

    frontend-starter-kit:使用Grunt进行自动化Web开发的前端入门套件

    单元测试(QUnit):支持在使用静态Web服务器的PhantomJS无头浏览器实例中使用进行单元测试。 ESLint:检查您的js文件中是否有错误。 Babel:为当前的浏览器转换基于ES6的JS文件。 Browserify:为当前浏览器捆绑...

    apollo-server-boilerplate

    使用Apollo服务器提供静态数据的样板项目。 该项目使用Babel将ES6从src转换为lib ES5。 该项目的结构超出了最初的Apollo和Graphql示例,以显示更加模块化的项目布局。 要查看如何将Apollo客户端连接到该服务器,请...

Global site tag (gtag.js) - Google Analytics