聊一聊 CommonJS和ES6 Module
为什么使用模块化?
模块化是指将一个复杂的系统分解为多个模块以方便编码。其实对于我们现在实际写的前端项目来说,文件即模块
,各种模块(文件)
构成我们的项目,然后模块之前通过一定的规则互相引用,然后在打包阶段,打包工具比如webpack
、rollup
等会在打包阶段去处理好我们模块的引用关系。
在以前古老的开发模块中,开发网页要通过命令空间的方式来组织代码,比如著名的jQuery库将它所有的API都挂到了window.$下,这样会存在一些问题:
- 命令空间冲突,两个库可能会使用同一个名称,例如Zepto
- 无法合理地管理项目的依赖和版本
- 无法方便地控制依赖的加载顺序
一旦项目过大,这种方式会变得难以维护,所以后面模块化的思想就诞生了。
1. commonjs
特点
- 动态引入,执行时引入
- 在运行后才可以得知模块导出内容,编译阶段无法做静态分析
- 输出的是值的拷贝
commonjs原理
我们先新建一个a.js
文件,内容如下:
// a.js
module.exports = 'a'
在同一个目录下新建一个b.js
文件:
// b.js
const fs = require('fs')
const path = require('path')
function req(targetPath) {
const absPath = path.resolve(__dirname, targetPath)
const content = fs.readFileSync(absPath, 'utf8')
const module = {
exports: {}
}
const fn = new Function('exports', 'module', 'require', '__dirname', '__filename', content + '\r\n return module.exports;')
return fn.call(module.exports, module.exports, req, __dirname, __filename)
}
console.log(req('./a.js')) // 打印a
核心原理就是利用node原生的fs模块
去读取文件,拿到代码字符串
,然后通过new Function
包装成一个函数去处理,然后通过自己在函数尾部新加的一行return module.exports
将执行后的结果抛出。
2. ES Module
- 静态引入,编译时引入,
- 只能作为模块顶层的语句出现,不能出现在function里面或者是if里面
- import的模块名只能是字符串常量
- 不管import的语句出现的位置在哪里,在模块初始化的时候所有的import都必须已经导入完成
- 输出的是值的引用
commonjs和es module使用场景
- 浏览器端代码:使用es2015 module(也就是
ES6 Module
),模块化使用灵活,且可充分利用tree shaking减少代码体积 - 服务端node:适合动态引入,一般不支持tree-shaking和es module,同时也并不需要考虑代码体积,所以使用commonjs模块规范,同时也可以拥有更好的debug支持,提高开发效率。
tree-shaking:
- tree-shaking可以利用ES2015(es6)模块语法静态分析的特性,删除没有使用的代码,对代码体积进行优化
- webpack tree-shaking开启条件:
- 使用es2015模块语法(import和export), require不行
- 配合JS代码压缩插件插件,如UglifyJSPlugin,TerserPlugin
- 去除babel-loader模块转换插件,不让babel-loader进行模块转换,以保留export和import关键字,然后让webpack来转换
小结
目前前端已经是模块化的天下了,学好ES module
和commonjs
就已经基本够用了,上面介绍了它们各自的特点和使用场景,希望能帮助到大家去更好的运用它们!
转载自:https://juejin.cn/post/7281151175982628903