likes
comments
collection
share

webpack如何转译commonjs模块和es模块

作者站长头像
站长
· 阅读数 95

在一个web项目中,我们可能会用到两种不同的模块:es模块和commonjs模块。对于这两种模块,使用require和import来加载得到的结果可能会是不一样的。如果我们要编写一个js库给别人用,导出一个不同类型的模块,对于使用者来说使用方式也会有不同。下面我们就来分析一下webpack是如何转译这两种模块的导出和加载的。

  1. commonjs
const myModule = 'module';
module.exports = myModule;
  1. es module
const myModule = 'module';
export default myModule;

打包一个简单的模块

假设我们写了这样一个模块:

class Person {
    constructor(name) {
        this.name = name;
    }
}
// es module
export default Person

然后我们使用webpack打包一个umd模块给其他人使用,webpack打包出来的umd模块大概长这样:

(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["Person"] = factory();
	else
		root["Person"] = factory();
})(self, function(){/*...*/})

可以看到,其他人使用我们的模块的时候,其实拿到的就是factory函数的返回值,这里我们不会探究factory函数的内部细节,有兴趣的读者可以去研究一下。我们只说结论:

  1. 对于es模块,webpack打包后,factory返回的值是这样的:
{
    default: class Person{/*...*/},
    _esModule: true,
    [Symbol.toStringTag]: 'Module',
    //...
}
  1. 对于commonjs模块,factory返回值直接就是导出值:
class Person(){/*...*/}

模块写完后我们把它发布到npm上,为了支持script标签引入,我们还把js发布到cdn服务器上,假设链接是 https://cdn.test.com/person.js

使用

使用script标签引入
<script src="https://cdn.test.com/person.js"></script>
<script>
    var sam = new Person('Sam');
</script>

然而打开页面我们会发现报错了,这是因为我们的模块是用export default导出的,所以Person.default才是我们要的构造函数。一个简单的解决方法是编写模块的时候使用module.exports导出。

使用npm

在使用了构建工具的web项目中,我们可能会使用require函数来加载模块,例如:

const Person = require('person');
const sam = new Person('Sam');

同样的,由于person模块是export default导出的,上面这种写法是会报错的,正确的写法应该是:

const Person = require('person').default;
const sam = new Person('Sam');

但是如果我们使用import语法来加载模块,就没有default这个烦人的属性:

import Person from 'person';
const sam = new Person('Sam');

为什么使用import语法就没有default的呢?别忘了,我们这里写的require和import都会被webpack转译。 使用require和import最终转译出来的模块加载函数是不一样的。如果使用import,编译后的代码在加载模块的时候,会判断这个模块是不是es module,是的话会重新定义getter函数,帮助我们正确地拿到模块导出值。

总结

通过以上的分析我们大概可以得出这两点结论:

  • 如果你想使用webpack把自己编写的代码编译成umd模块,使其在不同模块系统中的表现较为一致,那么源码最好写成一个commonjs模块。
  • 事实上输出es模块是比输出umd模块更好的选择:

ES模块是官方标准,也是JavaScript语言明确的发展方向,而CommonJS模块是一种特殊的传统格式,在ES模块被提出之前做为暂时的解决方案。 ES模块允许进行静态分析,从而实现像 tree-shaking 的优化,并提供诸如循环引用和动态绑定等高级功能。 所以对于一个在浏览器端使用的模块,我们最好还是编写成es模块,让用户使用import来加载。如果我们的模块需要同时支持浏览器和node环境,那么我们可以使用rollup打包输出两份代码,一份commonjs模块,一份es模块,然后通过设置package.json的module和main字段来实现兼容。

转载自:https://juejin.cn/post/7004125458723766280
评论
请登录