一篇搞懂AMD、CMD、CommonJS、EsModule、Node、Webpack之间的关系

1,953 阅读6分钟

一、模块化

本文的核心说的是模块化.

话说js语言最早使用的很少,页面逻辑一行js就能实现,就不存在模块化。这么少的代码还要什么模块 是吧1.jpg

当页面越复杂,开发人员越多,写的js代码越多; 这让人 2.jpg

为了解决两个问题:
1、首先防止全局代码逻辑混乱,需要根据业务逻辑划分模块;
2、其次是不同开发人员负责不同的模块,防止命名冲突或者是自己的声明的变量方法被别人误改,需要根据人员划分模块;

模块化概念就出现了!

模块化就是对js代码的划分,可以把一个js文件相当于一个模块,文件之间相互引用,就是模块的相互引用。

当前我们所熟知的Es module规范,并不是从模块化概念兴起的时候就制定的,直至ES6(2015)才推出。

按照时间顺序,在此之前的规范有AMD、CMD、CommonJs等等。

在node的大环境下,主流使用还是commonjs和esmodule,amd、cmd仅仅了解即可。

再来说明一个概念,规范和实现是不一样的东西,从名字就可以看出来,就想ESMA script是js语言的规范,但怎么执行js语言,这就需要node或浏览器实现,因为他们都有实现js代码的工具,就是JavaScript 引擎。

所以AMD 的主流实现是 requireJs
CMD的主流实现是 SeaJs
CommonJs的主流实现是node;

再来是执行环境:

环境node浏览器
AMD不可以可以
CMD不可以可以
commonjs可以可以
ES module可以(仅限打包后的代码)可以

AMD和CMD必须在浏览器中;
Commonjs和ES module既可以在node中也可以在浏览器中;

node环境下,Esmodule和commonjs规范下的代码不能互相混用,要想ES module在node环境下也能执行,需要wepack打包工具打包文件,将代码转换成node可执行的代码!

二、模块化之前

再来了解下在规范之前想使用模块化,是怎么解决的?

匿名立即执行函数

//声明
//声明一个区域,避免和其他人的代码冲突
const moduleA = (function(){
  const bar = 'c'
  function foo(a, b){
    return a + b
  }
  return {
    bar,
    foo    
  }
})()

//使用

//使用属性
moduleA.bar
//使用方法
moduleA.foo()

二、AMd和requireJs(了解)

AMD ==== Asynchronous Module Definition(异步模块定义)

1、js代码入口

//  demo/demo.html
 <!-- 引入require.js文件和一个入口文件main.js。main.js中配置require.config()并规定项目中用到的基础模块。 -->
 <script src="lib/require.js" data-main="js/main"></script>

data-main属性用来说明文件的入口,并且在requires.js加载完成后再执行代码。或者

  <script src="lib/require.js"></script>
  <script src="js/main.js" ></script>

2、模块注册require.config()

// js/main.js 主文件入口
require.config({
  baseUrl: "js",
  paths: {
    "jquery": "jquery-3.6.0.min",  //实际路径为js/jquery.min.js  //第三方模块
    "math": "math",             //实际路径为js/math.js  //定义模块
  }
})

paths: {key: path},中定义了模块使用的名称、模块的路径
既可以注册第三方模块,也可注册自定义模块

3、模块定义define()

// js/math.js
//  自定义math.js模块
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});

//第三模块需要自己下载,如jquery的jquery.min.js

4、模块使用require()

require(["jquery","math"],function($, _){
  // 在代码中用 $, _来使用
  console.log("$", $, $('#a'),)
  console.log("1+2=", _.add(1,2));
});

//使用单个模块,可以不用中括号
require("jquery",function($){...}

三、CMD和seaJs(了解)

CMD ==== Common Module Definition(通用模块定义)

cmd不需要注册,直接require()使用

1、 js代码入口

  <script src="./lib/sea.js"></script>
  <script>
      seajs.use('./js/main.js')
  </script>

2、注册并使用define((require, exports, module)=>{})

require,模块引入函数,require(path)
exports, 导出对象
module,导出对象

//  定义js/math.js模块,  math.js模块中也可依赖其他模块,只需要require引入即可
define(function(require, exports, module) {
  var basicNum = 0;
  var add = function (x, y) {
      return x + y;
  };
  //导出方式一:
  module.exports= {
    add,
    basicNum
  };
  //导出方式二:
  module.exports.add = add;
  //导出方式三:
  exports.add = add;
});

//js/main.js
define(function(require, exports, module) {
  var _ = require('./math')
  console.log("1+2=", _.add(1,2));
});

注意:不能通过exports = {},错误方式

四、CommonJs和Node(重头戏)

模块中的内容进行导出:exports和module.exports;
导入其他模块: require;require(自定义模块/系统模块/第三方库模块);

1、exports和module.exports

//导出变量、函数、类等
const bar = 1const foo = function(a, b){
    return a + b
}
//方式一: 这里module.exports = {} 不是一个对象,{}只是一个导出规则,必须要这样写
module.exports = {
    bar,
    foo
}

//方式二:
module.exports.bar = bar
module.exports.foo = foo

//方式三:
exports.bar = bar
exports.foo = foo

exports和module.exports 的本质

源码上的实际上是导出一个module.exports的引用,所以能用方式一 module.exports = {} 和 方式二 module.exports.bar = bar 导出内容;
源码中还多做了一步就是,exports = module.exports,让exports指向module.exports,这样两者都是指向同一个引用,所以能用方式三;

导出的是module.exports的引用,所以当我们想通过exports = {}导出时, 我们已经包exports的引用改掉了,这个时候,不论如何操作exports,里面的内容都是导不出去的。

module.exports = {}
exports = module.exports

2、require

  • require(模块名)
//直接找到内置模块, 这些内置模块是在安装node时,自动安装的,应该存放在安装目录里的一个node_modules文件中
const fs = require('fs')
const path = require('path')
  • require(路径)

require('./')
require('./')
require('../')

按照一下规则依次查找,直至找到

1、把require里的path当成一个文件路径; 查找顺序:

指定后缀名的文件 --》 .js后缀的文件 --》 .json后缀的文件 --》 .node后缀的文件

2、把require里的path当成一个目录路径

指定目录index.js文件 --》 指定目录index.json文件 --》 指定目录index.node文件

3、如果没有找到,那么报错:not found

  • require(X) 未知的东西

require(X) 引入未知的东西,既不是内置模块,也不是自定义模块

在每一层的node_modules中依次查找,直至找到

查找当前目录下node_modules文件 --》 上一层目录下node_modules文件 --》 ...

五、ESmodule和Webpack

具体规则可查看 阮一峰写的es6,上面写的很详尽,我就不多此一举了

主要说下,esmodule规范下的代码怎样在node和浏览器环境中执行:

问题

node执行Js代码采取的模块化规范是commonjs,node内部默认是不把js文件当做ES module,用node执行采取ES module规范的代码时,会出现如下错误:

//demo/main.js

import { add } from './utils.js'
console.log(add(1,1))

//demo/utils.js
export const add = (a, b) =>{
  return a + b
}

执行命令: node ./main.js image.png

解决

要想解决这个问题就需要让node把每个js文件当做一个模块! 按照提示解决: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.

  • 解决1: 生成一个package.json文件,在文件中设置"type": "module" npm init 生成文件

image.png

  • 解决2 在html中引用main.js文件
<script src="./main.js" type="module"></script>

注意:要通过开启一个本地服务在浏览器打开html!

通过script标签引用模块main.js,相当于发起一个请求,获取到模块里的内容,开启本地服务可以组织跨域。

通过本地服务127.0.0.1查看

image.png

通过文件路径查看

image.png

  • 解决3 webpack打包 1、npm init 生成package.json文件
    2、有全局webpack,在根目录下执行webpack命令;
    3、没有全局webapck,可npm install webpack webpack-cli -g,下载后,在执行webapck; 或者下载局部webpack,npm install webpack webpack-cli -D,执行npx webpack;

node中执行: node .\dist\main.js 或者

    <script src="./dist/main.js"></script>