编程技术文章分享与教程

网站首页 > 技术文章 正文

从systemjs的使用学习js模块化(js模块化开发教程)

hmc789 2024-11-17 11:18:09 技术文章 2 ℃

?前段时间,由于业务的拆分,原本一同维护的后台项目变成了两个团队来一起维护,这就带来了一些问题。例如:无法分开上线,公司的测试环境不够用等一些问题,加上之前这个项目经过时间的累积,长久的迭代变得模块越来越多,越来越难以维护,一次打包需要好几分钟,找一个文件也需要翻一会。所以,我们痛定思痛,决定用最近比较流行的微前端技术来改造他。经过微前端改造的项目已经上线,目前来看效果挺不错的。这次,先不谈微前端改造,之后再写,这次先写一写,我们在进行微前端框架搭建过程中使用的基础设施systemjs的使用,以及js模块化的几种方式。

模块化呢,他的出现是由于早期的js只是实现一些简单的交互(当时的表单提交都是采用form 配合action,直接提交到后端),随着计算机性能的提升,很多页面逻辑提升到了客户端来做,例如表单验证等,后来web2.0时代ajax的使用, 以及JQuery等前端库的出现,前端代码变的日益庞大,此时就需要考虑用模块化来拆分,组织代码。使其更易于维护。

模块化的优势

  1. 避免变量名冲突(全局变量污染)
  2. 更好的分离代码,可按需加载,提升性能
  3. 可维护性更高
  4. 可复用性更高

目前js模块化分为以下几种

  1. CommonJS
  2. AMD
  3. CMD
  4. UMD
  5. ES6 modules

我们一个一个来了解一下

CommonJS是一种同步化获取模块的模块加载方式,nodejs自带的模块管理就是CommonJS,基本语法:

导出:例如导出一个对象,里面包含一个函数test

module.exports = { test: function(){} };

导入:例如引入nodejs自带的文件管理库 (后面也可跟自定义的模块,一个文件地址)

const fs = require('fs');

AMD(Asynchronous Module Definition)是由Require js提出的, 通过异步来引入模块, 基本语法

首先引入并定义requirejs的配置

//引入requirejs  data-main的值指向的是requirejs加载完后,会执行这个文件
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.js" data-main='/main'></script>

// 在main中可定义requirejs的配置  
require.config({  paths: {    moduleA: '/modulea',  }});

导出:定义一个moduleA, 导出一个对象,里面包含函数test

// 如果没有依赖,define第一个参数可以直接是一个function,如果有依赖,第一个参数是一个数组,定义所依赖额模块
define(function() {  return {    test: function(){}  }});

导入:导入moduleA

require(['moduleA'],  function(moduleA) {});

CMD 由玉伯提出的(seajs)按需加载模块,和AMD的区别在于CMD是使用的时候才去加载,而AMD是在开始就声明需要依赖哪些模块

导出和导入都在一块,既可以导入,也可以导出

// require可以去引入别的模块,exports和module与CommonJS类似,放置导出的东西
define(function(require, exports, module){  
  const moduleA = require('/modulea'); 
  module.exports = {    test: function(){}  }
});

UMD(Universal Module Definition) 是AMD和CommonJS的综合,AMD通常在浏览器端使用,异步加载模块,CommonJS通常在服务端使用,同步加载模块,为了能兼容,所以想出了这么一个通用的解决方案

UMD源码

(function(root, factory){ 
  // 先判断是否支持AMD(define是否存在)  
  if(typeof define === 'function' && define.amd) {   
    // 如果有依赖,例如:define(['vue'], factory); 下面同理,采用对应的模块化机制,加载依赖项    
    
    define(factory);  // 判断是否支持CommonJS模块格式      } else if(typeof module === 'object' && module.exports) {    module.exports = factory();  // 前两种都不存在,将模块挂载到全局(window或global)  moduleName代表模块名称   } else {    root.[moduleName] = factory();  }}(this, function(){  return {    test: function(){}  };}))

ES6 Modules

ECMAScript 6 简称ES6,是javascript语言的标准,在ES6中正式推出了模块化,ES6模块化的特点是静态引用,使得编译时就能确定模块的依赖关系,以及输入和输出的变量,Tree Shaking就是基于ES6的模块化来实现的

基本语法:导出

function test(){}  
export default test;

导入

import test from './test';

简单说了下目前的几种模块化机制,接下来说说今天的主角systemjs,systemjs的官方定义是为浏览器中的ES模块启用向后兼容工作流和可配置的模块加载程序,说的简单点就是让你可以在浏览器中使用上述说的几种任意的模块化方式。systemjs在我们的微前端框架中起到了很大的作用,我们使用它来引用各个模块,接下来,让我们看下如何使用它

首先,引入systemjs主文件,使用cdn的systemjs

<script src="https://cdn.bootcss.com/systemjs/6.2.6/system.js"></script>

引入主文件后,就可以简单使用了

// 通过systemjs来引入别的文件
System.import('./test.js');
// systemjs也支持通过下面的方式定义资源 ,用来给资源定义一个key
<script type="systemjs-importmap"> 
  {    
     "imports": {      
        "vue": "https://cdn.bootcss.com/vue/2.6.11/vue.js"    
     } 
  }
</script>
// 直接通过名称引用
System.import('vue');

如果我们只是用它来引入别的js文件通常按上面这种方式使用就够了,但通常不会这么干,更多的时候是做我们的模块管理,这个时候我们就需要使用到一个systemjs的插件

// systemjs用来解析UMD的插件
<script src="https://cdn.bootcss.com/systemjs/6.2.6/extras/amd.js"></script>
// 如果是使用ES6 modules 需要使用下面这个插件,将default的内容取出来
<script src="https://cdn.bootcss.com/systemjs/6.2.6/extras/use-default.js"></script>

接下来我们就可以用systemjs来加载通过模块化导出的文件了, 并可以拿到导出的内容, 例如:

// 我们的一个模块是这么导出的
(function(root, factory){
  if(typeof define === 'function' && define.amd) {
    define(factory);  
  } else if(typeof module === 'object' && module.exports) {
    module.exports = factory();
  } else {
    root.test = factory();
  }
}(this, function(){
  return {
    test: function(){}
  };
}));
// 在.then中我们就拿到了上面这个test模块导出的test方法
System.import('./test.js').then(res => {
  console.log(res);
});

如果我们要加载的模块项有依赖,需要处理依赖项,这个时候我们上面说过的script的type为systemjs-importmap的对象就起作用了,它用来给资源定一个key,例如下面的模块是我们用webpack打包后的一个模块,他依赖了vue,但是由于我们整个项目有共通引用vue,在打包的时候就通过external将vue排除,缩小打包后的文件大小。

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory(require("vue"));
  else if(typeof define === 'function' && define.amd)
    define(["vue"], factory);
  else {
    var a = typeof exports === 'object' ? factory(require("vue")) : factory(root["vue"]);
    for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
  }
})(window, function(__WEBPACK_EXTERNAL_MODULE_vue__) {
   console.log(__WEBPACK_EXTERNAL_MODULE_vue__);
   return {};
});

上面的代码就是我经过缩减后的webpack打包的模块导出,可以看到如果我使用了external,webpack就会通过umd的方式,去加载依赖项,并将依赖通过参数传入,这个时候如果我们通过systemjs的importmap定义了vue,就会将vue对应的资源的导出传入这个函数。假定这个模块的文件地址是 /webpackbundle.js

接下来看下如何导入这个模块

// 首先定义vue资源
<script type="systemjs-importmap">
  {
    "imports": {
      "vue": "https://cdn.bootcss.com/vue/2.6.11/vue.js",
      "webpackBundle": './webpackbundle.js'
    }
  }
</script>
<script>
  // 引入vue之后,再引入上面导出的模块, 这个时候依赖项vue就被传入webpackbundle了
  System.import('vue').then((res) => {
    System.import('webpackBundle');
  })
</script>

以上就是我们通过systemjs实现的模块管理,更详细的使用方式可以查看systemjs的github:https://github.com/systemjs/systemjs

标签列表
最新留言