原生ECMAScript模块(ECMAScript modules)概述

来源:互联网 发布:java volatile 原子类 编辑:程序博客网 时间:2024/06/08 07:03
本文标签:   JavaScript ECMAScript模块 ES6模块 CommonJS Safari技术服务器

2016年浏览器和Node.js对于ECMAScript 2015 specification的应用取得了难以置信的发展。现在我们面临的状况是支持情况几乎都接近100%:

但是标准也同时介绍了ECMAScript modules (今天也经常被叫作ES或者ES6模块)。它仅仅是会(并且会继续)花最多的时间来实现这一标准,因为还没有浏览器在稳定版本中实现。 最近 Safari 10.1 (Safari Beta), Firefox 54 (Nighly) 以及Edge 15 (next EDGE version)或在之后的标志下 即将实现原生的开箱即用,因此我们很快就可以不需要模块打包来实现。 为了更好地了解我们如何来到这一点,让我们从JS模块历史开始,然后看看当前Native ES模块的特性和实现。

历史

历史上JavaScript没有提供模块系统。有很多技术,我主要说一下最典型的: 1) script 标签里面的长脚本 E.g.:

1<!--html-->2<script type="application/javascript">3    // module1 code4    // module2 code5</script>

 

2) 使用script 标签逻辑地将文件进行划分:

1/* js */2 3// module1.js4    // module1 code5 6// module2.js7    // module2 code1<!--html-->2<script type="application/javascript" src="PATH/module1.js"></script>3<script type="application/javascript" src="PATH/module2.js"></script>

 

3) 模块作为一个函数(一个模块就是一个返回自我调用函数或者JavaScript构造函数的函数)

  • 应用程序文件/模型作为应用程序的入口点:
01// polyfill-vendor.js02(function(){03    // polyfills-vendor code04}());05 06// module1.js07function module1(params){08    // module1 code09    return module1;10}11 12// module3.js13function module3(params){14    this.a = params.a;15}16 17module3.prototype.getA = function(){18    return this.a;19};20 21// app.js22var APP = {};23 24if(isModule1Needed){25    APP.module1 = module1({param1:1});26}27 28APP.module3 = new module3({a: 42});1<!--html-->2<script type="application/javascript" src="PATH/polyfill-vendor.js"></script>3<script type="application/javascript" src="PATH/module1.js"></script>4<script type="application/javascript" src="PATH/module2.js"></script>5<script type="application/javascript" src="PATH/app.js"></script>

 

之后社区发明了真正的分离技术来继续这种分离。主要的想法是提供一个系统,它将允许你只是包括一个链接到JS文件,如:html和其他一切都在bundler端。

让我们来看看主要的JavaScript模块技术标准:

异步模块定义(AMD)

这个方法与RequireJS库以及工具(如r.js)来构建结果打包。常用的语法是:

01// polyfill-vendor.js02define(function () {03    // polyfills-vendor code04});05 06// module1.js07define(function () {08    // module1 code09    return module1;10});11 12// module2.js13define(function (params) {14    var a = params.a;15 16    function getA(){17        return a;18    }19 20    return {21        getA: getA22    }23});24 25// app.js26define(['PATH/polyfill-vendor'] , function () {27    define(['PATH/module1', 'PATH/module2'] , function (module1, module2) {28        var APP = {};29 30        if(isModule1Needed){31            APP.module1 = module1({param1:1});32        }33 34        APP.module2 = new module2({a: 42});35    });36});

 

CommonJS

它是Node.js生态系统中的基础JS打包。Browserify是使用它作为构建的主要工具之一。本标准的显著特点是为每个模块提供一个单独的作用域。 如下所示:

01// polyfill-vendor.js02    // polyfills-vendor code03 04// module1.js05    // module1 code06    module.exports= module1;07 08// module2.js09module.exports= function(params){10    const a = params.a;11 12    return {13        getA: function(){14            return a;15        }16    };17};18 19// app.js20require('PATH/polyfill-vendor');21 22const module1 = require('PATH/module1');23const module2 = require('PATH/module2');24 25const APP = {};26 27if(isModule1Needed){28    APP.module1 = module1({param1:1});29}30 31APP.module2 = new module2({a: 42});

 

ECMAScript模块 (aka ES6/ES2015/Native JavaScript modules)

另外一个标准是 ES2015。它带来了社区需要的语法和功能。

  • 单独的模块作用域
  • 默认strict模式
  • 循环依赖
  • 你可以轻松地按照规范拆分代码 有一些加载器/编译器/方法支持系统中的一部分或者全部,例如:
  • Webpack

  • SystemJS

  • JSPM

  • Babel

  • UMD

工具

对于今天,我们习惯使用工具来打包JavaScript模块。如果我们谈论到ECMAScript,您可以使用以下方法之一:

  • Rollup

  • Traceur Compiler

  • Babel,特别的ES2015 modules to CommonJS transform 插件

  • Webpack 2 也带来了Tree Shaking (删除未使用的引入)

通常该工具提供CLI和或或者配置和打包JS文件的能力。它获取入口点并且打包您的文件,通常加入use strict以及某些为了让代码在所有环境(老版本浏览器,Node.js等等)而对单吗进行转换。 让我们看看简化的Webpack配置,它设置了入口点并且使用Babel来处理JS文件:

01// webpack.config.js02const path = require('path');03 04module.exports = {05  entry: path.resolve('src', 'webpack.entry.js'),06  output: {07    path: path.resolve('build'),08    filename: 'main.js',09    publicPath: '/'10  },11  module: {12    loaders: {13     "test": /\.js?$/,14     "exclude": /node_modules/,15     "loader": "babel"16   }17  }18};

 

配置的主要意思是: 1. 从webpack.entry.js 文件启动

2. 对于所有的JS文件应用Babel加载器(意味着代码将根据预置/插件+bundled来进行转化)

3. 将结果放入main.js文件

在这种情况下,通常index.html包含以下内容:

1<script src="build/main.js"></script>

 

那么你的app使用的是打包以及转换后的JS代码。上述是使用打包的常见方法,接着让我们看看如何在浏览器中不使用打包来进行模块划分。

如何使JavaScript模块在浏览器中工作

浏览器支持

现如今,每个主流浏览器都在向支持ES6模块发展:

  • Firefox- 实现,在Firefox 54+中的标志下可用

  • Chrome- 正常工作

  • EDGE- 实现,在EDGE 15+标志下可用

  • Webkit- 实现,默认情况下在Safari 10.1中启用

  • Node.js- 正在审议中,需要进一步讨论

获取环境来测试

正如我们看到的,目前您可以在Safari 10.1+和EDGE 15 Preview Build中测试本机JS模块。让我们下载并启用其中的功能:

在Firefox中使用ES模块

目前你必须下载Firefox Nightly,这意味着它很快就会到来FF Developer Edition 并且会接着在稳定版本中实现。

启用ES模块:

  • 打开about:config页面
  • 点击“我接受风险!”
  • 搜索dom.moduleScripts.enabled启用标志
  • 双击并且将它的值转化为"true"

就这样,现在你可以测试模块脚本如何在FF中工作。

在启用ES模块的情况下获取Safari技术预览

如果您使用macOS,只需从developer.apple.com下载最新的Safari技术预览(TP)安装并打开它。

从Safari 技术预览版本21+开始,并且会默认启用ES模块。

如果是Safari TP 19或20,请检查ES6模块功能是否已启用,打开“开发”菜单 - >“实验功能” - >“ES6模块”。

另一个选项是下载最新的Webkit Nightly并使用它。

启用ES模块的EDGE 15

你可以从Microsoft下载免费的虚拟机。

只需选择虚拟机(VM)“Win 10预览版(15.XXXXX)上的Microsoft EDGE”以及例如 “虚拟盒”(也免费)作为平台。

安装并运行VM,并打开EDGE浏览器。

打开about:flags页面,然后选中“启用实验性JavaScript功能”复选框

就这样,现在你有2个环境,你可以使用ECMAScript模块的本地实现

原生和捆绑模块的区别

让我们从本地模块功能开始:

1. 每个模块都有自己的作用域,而不是全局作用域 2. 它们总是处于严格模式,即使没有提供“use strict”指令 3. 模块可以使用import指令导入其他模块 4. 模块可以使用导出来导出绑定

到目前为止,我们没有面对与我们习惯于使用打包的巨大差异。

最大的区别在于为浏览器提供入口点的方式。您必须提供具有特定属性type =“module”script标签,例如:

1<script type="module" scr="PATH/file.js"></script>

 

这告诉浏览器你的脚本可能包含其他脚本的导入,他们应该被相应地处理。

这里出现的主要的问题是:

为什么JavaScript解释器无法自行检测文件是否是模块?

这里是其中一个原因

模块默认处于严格模式,经典的 scripts- no:

1. 让我们说解释器解析文件,假设它是一个不是严格模式的经典脚本

2. 然后找到“import \ export”指令

3. 在这种情况下,它应该从头开始在严格模式下再次解析所有代码

另一个原因 - 相同的文件可以没有严格模式的有效,并与它无效。然后有效性取决于它被解释的方式,这导致意想不到的问题。

定义期望的文件加载的类型为许多优化方法创造了条件。例如,在解析文件的其余部分之前并行加载文件导入)。你可以找到一些使用Microsoft Chakra JavaScript引擎对于ES模块的示例。

Node.js将文件标记为模块的方式

Node.js的性质不同于浏览器和使用``的解决方案也不适用。

目前,[合适的解决方案仍在讨论}(https://github.com/nodejs/node/wiki/ES6-Module-Detection-in-Node)。

有一些解决方案被社区否决了:

1. 在每一个文件添加"use module";

2. Meta在package.json中

其他选项仍在考虑中(感谢@bmeck的突出贡献):

1. 决定源代码是否是ES 模块

2. 在ES6模块中使用新的文件拓展名.mjs,如果之前的版本没有办法工作工作的话,这个选择可以作为备选。

1每个方法都各有利弊,目前,[Node.js如何发展还是未知的](https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md))。

 

简单原生模块例子

好的,首先让我们先创建一个简单的演示(您可以在您设置的环境中运行来进行测试。因此,它将是一个简单的模块,它会引入另外一个模块并且调用其中的一个方法。第一步-使用``来包括这个文件:

1<!--index.html-->2 3 4   5    <script type="module" src="main.js"></script>6   7   8  

 

模块文件

1// main.js2import utils from "./utils.js";3 4utils.alert(5  JavaScript modules work in this browser:6  https://blog.whatwg.org/js-modules7);

 

最后,引入的utils:

1// utils.js2export default {3    alert: (msg)=>{4        alert(msg);5    }6};

 

您可能会注意到,当使用import指令的时候,我们会提供.js文件拓展名。这是和通常的打包行为的另外一个区别-原生模块不会默认添加.js拓展名。

实际上,这意味着你必须提供确切的URL。主要的要求是资源应该有一个正确的MIME类型(感谢@bradleymeck纠正这个)

第二,让我们来检查模块的作用域(演示):

1var x = 1;2 3alert(x === window.x);//false4alert(this === undefined);// true

 

第三-我们会检查原生模块是在严格模式下。例如,严格模式禁止删除纯名称。因此下面的演示会显示出在模块脚本中抛出错误:

01// module.js02var x;03delete x; // !!! syntax error04 05alert(06    THIS ALERT SHOULDN'T be executed,07    the error is expected08    as the module's scripts are in the strict mode by default09);10 11// classic.js12var x;13delete x; // !!! syntax error14 15alert(16    THIS ALERT SHOULD be executed,17    as you can delete variables outside of the strict mode18 );

 

严格模式在模块脚本中是不可避免的。

注意

  • .js拓展名不能被省略(应该提供确切的 URL)

  • 作用域不是全局的, this 不指向任何变量

  • 原生模块默认处于严格模式下 (不再需要提供‘use strict’ )

内联模块脚本

和经典的脚本一样,你可以内敛代码,而不是在单独的文件中提供它。在之前的演示中,你可以将main.js直接在放在 ``之中,这会导致相同的行为:

1<script type="module">2  import utils from "./utils.js";3 4  utils.alert(5    JavaScript modules work in this browser:6    https://blog.whatwg.org/js-modules7  );8</script>

 

注意

  • 以执行脚本或加载外部文件并作为模块使用执行它

浏览器如何加载并且执行模块

模块都是默认延迟加载的。为了理解这一点,你可以想象它们都具有一个默认的defer属性。

下面是规范中解释这一行为的图片:

alt

这意味着,默认模块脚本不阻塞,并行加载,并在页面完成解析时执行。

因此您可以通过添加async属性来改变这种行为,因此这个脚本只会在加载的时候执行。

默认行为和经典主要的区别是:经典脚本会被立即读取和计算,直到这两步完成才会开始解析。

为了表示它,下面是一个带有脚本选项的演示,在这第一个将是一个没有 defer \ async 属性的经典脚本:

1<script type="module" src="./script1.js"></script>2<script src="./script2.js"></script>3<script defer="" src="./script3.js"></script>4<script async="" src="./script4.js"></script>5<script type="module" async="" src="./script5.js"></script>

 

其它顺序取决于浏览器的实现,脚本的大小,导入脚本的数量等。

写在最后:FOR Freedom 看看外边的世界,以及IT这一行,少不了去Google查资料,最后,安利一个国外网络加速器。一枝红杏加速器,去Google查资料是绝对首选,连接速度快,使用也方便。我买的是99¥一年的,通过这个链接(http://my.yizhihongxing.com/aff.php?aff=2509)注册后输上会员中心得优惠码,平摊下来,每月才7块钱,特实惠。

本文标签:   JavaScript ECMAScript模块 ES6模块 CommonJS Safari技术服务器

转自 SUN'S BLOG - 专注互联网知识,分享互联网精神!

原文地址: 《原生ECMAScript模块(ECMAScript modules)概述

相关阅读:Aaron Swartz – 互联网天才开挂的人生历程:每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的?
相关阅读:网站环境apache + php + mysql 的XAMPP,如何实现一个服务器上配置多个网站?

相关阅读:什么是工程师文化?各位工程师是为什么活的?作为一个IT或互联网公司为什么要工程师文化?

相关阅读: 对程序员有用:2017最新能上Google的hosts文件下载及总结网友遇到的各种hosts问题解决方法及配置详解

相关阅读: 《win10永久激活教程以及如何查看windows系统是不是永久激活?》

相关BLOG:SUN’S BLOG - 专注互联网知识,分享互联网精神!去看看:www.whosmall.com

原文地址:http://whosmall.com/?post=237

0 0