Skip to content

前端模块化 commonJS & ESM #4

@nia3y

Description

@nia3y

commonJS

什么是 commonJS

[Modules: CommonJS modules | Node.js v22.2.0 Documentation]

commonJS 是一种前端模块化规范

  1. 每个文件有自己单独的作用域,在其中定义的所有变量、函数、类都是私有的

  2. 通过 module.exports 来对外暴露值、通过 require 来加载 module.exports 暴露的值

  3. 如果想要多文件共享,可以把功能定义到全局变量上(不推荐的🙅‍)

module 对象

每一个模块都有一个 module 对象,有以下属性:

const module {
	id: '.',
	path: 'D:\\work\\commonjs\\tests',
	exports: {}, // 模块对外输出的值
	filename: 'D:\\work\\commonjs\\tests\\index.js',
	loaded: false, // 该模块是否加载完成
	children: [], // require 的模块信息
	parent: [] // 调用该模块的模块信息
	paths: [ // 模块的搜索路径
	  'D:\\work\\commonjs\\tests\\node_modules',
	  'D:\\work\\commonjs\\node_modules',
	  'D:\\work\\node_modules',
	  'D:\\node_modules'
	]
}

exports 对象

exports 对象是对 module.exports 的引用

var exports = module.exports

模块中的 this 也指向这个 exports 对象

require 方法

require 用来读取一个 JS 文件,并返回 exports 对象:

const bar = require('./foo.js');

// 读取 foo.js 并把 exports 赋值给 bar

文件解析规则:

  1. 如果路径以 / 开头,则加载绝对路径的模块文件

  2. 如果以 ./ 开头,则加载相对路径文件

  3. 否则加载内置的模块 或者 根据当前路径,递归查找 node_modules 下的模块文件并结合 package.json 进行解析:

    [Modules: Packages | Node.js v22.1.0 Documentation]

  4. 如果指定的模块文件没有发现,Node 会依次添加 .js.json.node 后缀并再次查找

具体的算法:

[Modules: CommonJS modules | Node.js v22.2.0 Documentation]

代码示例加载逻辑:

Moldule._load = function(request) {
	// 1. 判断是否命中 Module.cache
	// 2. 没命中的话执行 module._load 加载模块
	// 3. 执行 module._compile 执行文件
	// 4. 缓存执行结果,并返回 module.exports
}

Module.prototype._compile = function(content) {
	const module = {
		exports: {},
		...
	}
	// 执行文件
	// (function (exports, require, module, __filename, __dirname) {
	  // content
	// })();
	return module.exports
}

require 对象

const require = [Function: require] { // 既是函数也是对象
	resolve: [Function: resolve] // 返回加载的绝对地址
		{
			paths: [Function: paths] // 获取 path 的查找路径
		},
  main: { // 入口模块
    id: '.',
    path: 'D:\\work\\commonjs\\tests',
    exports: {},
    filename: 'D:\\work\\commonjs\\tests\\index.js',
    loaded: false,
    children: [ [Object] ],
    paths: [
      'D:\\work\\commonjs\\tests\\node_modules',
      'D:\\work\\commonjs\\node_modules',
      'D:\\work\\node_modules',
      'D:\\node_modules'
    ]
  },
  extensions: [Object: null prototype] { // 不同后缀执行的加载方法
    '.js': [Function (anonymous)],
    '.json': [Function (anonymous)],
    '.node': [Function (anonymous)]
  },
  cache: [Object: null prototype] { // 缓存的模块信息
    'D:\\work\\commonjs\\tests\\index.js': {
      id: '.',
      path: 'D:\\work\\commonjs\\tests',
      exports: {},
      filename: 'D:\\work\\commonjs\\tests\\index.js',
      loaded: false,
      children: [Array],
      paths: [Array]
    },
    'D:\\work\\commonjs\\tests\\import.js': {
      id: 'D:\\work\\commonjs\\tests\\import.js',
      path: 'D:\\work\\commonjs\\tests',
      exports: [Object],
      filename: 'D:\\work\\commonjs\\tests\\import.js',
      loaded: true,
      children: [],
      paths: [Array]
    }
  }
}

循环加载

// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';

// b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';

// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);

// output

// b.js a1
// a.js b2
// main.js a2
// main.js b2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions