2

JS/TS项目里的Module都是什么?

 2 years ago
source link: https://juejin.cn/post/7086645747013124126
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文分享自华为云社区《JS/TS项目里的Module都是什么?都有几种形式?loaders和bundlers的区别是什么?》,作者: gentle_zhou 。

在日常进行JS/TS项目开发的时候,经常会遇到require某个依赖和module.exports来定义某个函数的情况。再加上在日常审视代码的时候,发现tsconfig.json文件里有一个"compilerOptions",里面关于module引入的是"commonjs",就很好奇Modules都代表什么和有什么作用呢。

本文会分为如下几个部分进行介绍:什么是Module?为什么我们需要Modules?几种常见的Module形式;Module loaders和Module bundlers的区别。

什么是Module?

一个Module(模块)顾名思义就是一段可以重复利用的代码(通常是一个特性,或则一些特性的集合;可以是一个文件或则多个文件/文件夹的集合),它封装了内部代码实现的细节并曝露一个公开的API,让其他代码可以轻易地加载和使用。

为什么我们需要Modules?

技术上来说,其实完成一个JS/TS项目,我们并不需要模块,直接上手写代码也是可以的。但就像在JAVA、Python软件项目里,不引入依赖一样,会导致程序员们重复写很多相同的代码。

引入Module,为的就是可以应对JS/TS项目的代码越来越庞大,越来越复杂的情形。我们需要使用软件工程的方法,来管理JS/TS项目的业务逻辑。

在JS/TS项目中,模块应该允许我们实现以下功能:

  • 抽象代码:将功能委托给专门的库,这样我们就不必了解它们内部实际如何实现的(无论多复杂)
  • 封装代码:如果我们不想再更改代码了,可以将代码隐藏在模块中
  • 重用代码:避免反复编写相同的代码
  • 管理依赖:在不重写代码的情况下,轻松改变依赖关系

几种常见的Module形式

在 ES6 Module 出现之前,在ES5时期,JS并没有提供一个官方的定义模块的规则;因此JavaScript 社区里的天才程序员们尝试了各种形式来定义模块,以达到“在现有的运行环境下,可以实现模块效果”的目的。

一些非常有名的模块形式:

  • CommonJS CommonJS形式是用在Node.js环境里的,我在文章开头提到的requiremodule.exports就是CommonJS里用来定义依赖和模块的:

      var dep1 = require('./dep1');  
      module.exports = function(){  // ...}
    复制代码
  • Asynchronous Module Definition (AMD) AMD(官方github链接)则是用在浏览器中的,顾名思义这个形式是异步的,其中用define函数来定义模块:

      // 一个依赖数组&一个工厂函数以参数的形式调用define函数
      define(['dep1', 'dep2'], function (dep1, dep2) {
      //通过返回一个值来定义模块值
      return function () {};
      });
    复制代码
  • Universal Module Definition (UMD) UMD则是可以用在浏览器和Node.js中,是通用的:

      (function (root, factory) {
        if (typeof define === 'function' && define.amd) {
          // AMD. 以同步模块的方式注册.
            define(['b'], factory);
        } else if (typeof module === 'object' && module.exports) {
          // Node节点. 不能和严格意义上的CommonJS一起使用,但是类似CommonJS的环境里是支持使用module.expoerts的,就像node.
          module.exports = factory(require('b'));
        } else {
          // 浏览器 globals (根节点是window)
          root.returnExports = factory(root.b);
        }
      }(this, function (b) {  
        // 返回一个值来定义module export;这里返回的是一个对象,但是模块其实可以返回一个函数作为exported value.
        return {};
      }));
    复制代码

以及现在出现的官方ES6 模块形式,一种原生的模块形式。它用export来输出模块的公开API:

// 输出函数
export function sayHello(){  
  console.log('Hello');
}
复制代码

我们可以使用import和as来引入部分代码到模块里:

import { sayHello as say } from './lib';

say(); // 输出Hello
复制代码

或则直接在一开始引入整个模块:

import * as lib from './lib';

lib.sayHello();  // 输出 Hello
复制代码

Module loaders和Module bundlers的区别

两者都是为了让我们编写模块化JS/TS应用的时候更方便快捷。

Module loaders

模块加载器用来解析并加载以特定模块格式编写的模块,通常是一些库;可以加载、解释和执行使用特定模块格式/语法定义的JavaScript模块,比如AMD或CommonJS。

在编写模块化JS/TS应用程序时,通常每个模块都有一个文件。因此,当编写由数百个模块组成的应用程序时,要确保所有文件都以正确的顺序包含进去可能会非常痛苦。所以,如果有加载器会为你负责依赖管理,确保所有模块在应用程序执行时被加载,那会轻松容易很多。

模块加载器是在运行时(runtime)运行的:

  • 在浏览器中加载模块加载器
  • 告诉模块加载器加载哪个主应用文件
  • 模块加载器下载并解析主应用文件
  • 模块加载器根据需要去下载文件

如果你试着在浏览器的开发人员控制台中打开network选项卡,将看到许多文件是按需由模块加载器加载的:

1.png

一些流行的模块加载器的例子如下:

  • RequireJS: AMD格式的模块加载器
  • SystemJS: AMD, CommonJS, UMD或System.register格式的模块加载器

Module bundlers

模块绑定器相当于是模块加载器的替代品;基本上,它们做的事情是一样的(管理和加载相互依赖的模块)。

但模块绑定器和加载器不同的地方是,它并非是在运行时运行的,而是作为应用程序构建的一部分运行(在build的时候运行);而且它是在浏览器中加载的。因此,绑定器在执行代码之前会将所有模块合并到一个文件/bundle中(比如叫bundle.js),而不是在代码运行时再去加载出现的依赖项。比如现在流行的两个bundlers:Webpack(AMD,CommonJS, es6模块的bundler)和Browserify(CommonJS模块的bundler)。

什么时候更适合用哪个呢?

这个问题的答案取决于JS/TS应用程序的结构与大小。

使用bundler的主要优点是,它让浏览器需要下载的文件变少了很多,这可以给我们的应用程序带来性能上的优势(因为减少了加载所需的时间);但是取决于应用程序的模块数量,并不是说用bundler就一定是最好的。对于那种大型应用(有很多模块),模块加载器可以提供更好的性能,因为bundler在一开始加载一个巨大的单文件会阻碍应用的启动。

如何选取,其实只需要我们进行测试比较一下即可~

点击关注,第一时间了解华为云新鲜技术~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK