🎁 import 还是 require
!本篇文章过于久远,其中观点和内容可能已经不准确,请见谅!~
写代码的时候不能不知其所以然,在现在工程化程度特别高的时代,很多配置都有十分成熟的脚手架工具,一行代码开箱即用的体验。但是这些东西都是辅助的工具箱,我们即将秃顶的艺术家更需要注重基础,不然很多时候项目报错都会让自己不知所措,或者会出现“明明自己弄得没错怎么还是没法运行,一阵鼓捣后有莫名其妙成功运行的奇妙过程”。而应接不暇的工具和框架就像是时代的洪流,你唯一能追的上的是自己解决问题的能力。
谁还记得当前 CMD, AMD, Commonjs 还有说不清楚的依赖处理和文件引入而出现的很多标准和实现。现在初学者小年轻上手就是 Vue,React 等框架,日常开发就是调调组件,拼拼界面,左边移一点,颜色深一点的资深开发工程师,具体在使用过程中很多的工程化实践都在 webpack 这个黑盒中处理了,甚至 webpack 都是别人配置好的,导致甚至有些人会觉得文件的引用就是支持多种写法而已:
var a = require('./a.js');var a = require('./a.js').default;import a from './a';import { a } from './a';import { b as a } from './a';import * as c from './c';module.exports = {a: 'A'};exports.a = 'A';exports.default = 'A';export default 'A';export const a = 'A';
涉及:
ES5
、ES6
、Node.js
、Babel
、Typescript
、语法检查、Webpack
、相关配置等。ES5
下仅支持辅助函数下的AMD
、CMD
、Commonjs
ES6
支持ES5
的模块封装,同时原生添加了模块化语法esModule
Node.js
生态比较早,模块由node
确定统一的Commonjs
语法ts
文件的模块化沿用了ES6
的模块方法(也提供了兼容Commonjs
的方法,但是用的很少)
以上都是统一干净的模块导入导出,如果混用呢?
ES6
能够兼容 ES5
的模块解决方案,但是 ES6
、TS
的模块如果要在 ES5
、Node
环境下使用就需要预编译了。(Node
的模块先不说)预编译的部分
Babel
和 tsc
怎么处理的?babel 默认将 esModule 转为 ES5 的 Commonjs 语法 (.babelrc 可以指定编译成 esModule)
// esModuleexport default () => {};export const a = 1;const b = 2;const c = 3;export { b, c };// esModule 转成的 Commonjsexports.default = function(){}; // 这也是 commonjs 引入 require('./a').default 的原因exports.a = 1;exports.b = 2;exports.c = 3;exports.__esModule = true;// 将 export 赋值给 exports,并带上一个标志 __esModule 标志原本是 esModule
babel 编译是单文件级别的,多文件联合起来需要提供
require
/exports
的实现,这个是 webpack 做的,__webpack_require__
直接可以使用 babel 的编译结果,并且打包合并全部模块,让整个逻辑能够运行,模块的处理是 Babel
处理的。上面只是语法的转换,引入的意图和语法还是有差异的:
// Commonjs 引入 esModule 的默认导出(比较特殊)var a = require('./a.js').default
其他引入方式按照编译后的结果正常使用
新语法引入旧语法基本上按照之前的意图,新语法编译的 Commonjs 怎么引入旧语法下的 Commonjs 语法,尤其处理 default、通配符和析构引入。
default 问题
// es6下:// import a from './a.js';function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}// a.js 是 esModule 文件的话引入的是 default// a.js 是 Commonjs 文件的话引入的是 module.exports
* 通配符问题
// es6 下:// import * as a from './a.js';function _interopRequireWildcard(obj) { // 精简if (obj && obj.__esModule) {return obj;}else {var newObj = {};if (obj != null) {for (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key))newObj[key] = obj[key];}}newObj.default = obj;return newObj;}}// a.js 是 esModule 文件的话引入的是 exports 整体// a.js 是 Commonjs 文件的话引入的是 module.exports 再加上一个 default 本身
析构引入
// es6下:// import { a } from './a.js';// 转为 Commonjs 下:// require('./a.js').a
通过 babel 的辅助运行时函数(transform-runtime 插件),可以实现多种规范模块的互相引用,引用意图也做了转换。
默认情况下 ts 是不允许多种规范的互操作,但是可以修改:
// tsconfig.json{// 语法检查上允许没有 default 的引入"allowSyntheticDefaultImports": true,// es 模块与其他模块的互操作,需指定 module 字段"esModuleInterop": true,"module": "commonjs",}
配置互操作之后,引入部分可以引入
Commonjs
规范,导出也可以导出可以被 require
的模块。只是这样的话编译完成的代码中,即时语法目标是 ES6
,但是模块关系都是 Commonjs
规范了。下面的语法场景:
// Commonjs 中引入外部模块var a = require('./a.js');// Commonjs 中引入 esModule(比如 Commonjs 的模块引入第三方 esModule 标准的 npm 包)var a = require('./a.js').default;// esModule 中 引入 esModule 的 default 导出,或者引入 Commonjs 的 module.exportsimport a from './a';// esModule 中 引入 esModule 的 export a,或者引入 Commonjs 的 exports.aimport { a } from './a';// esModule 中 引入别名import { b as a } from './a';// Commonjs 中导出默认module.exports = {a: 'A'};// Commonjs 中导出 aexports.a = 'A';// Commonjs 中导出 default 名称exports.default = 'A';// esModule 中导出默认export default 'A';// esModule 中导出 aexport const a = 'A';
很多人在使用 webpack 的时候没有考虑过怎么使用 这个加载方式的问题,有些人写 node 比较多,经常使用 require 的方式引入依赖,另一些人喜欢使用最新语法,所以一般都是 import from,这两种方式混写 webpack 都能正确的打包出来。
首先 webpack 本身简单说就是提供了文件的打包过程,整个处理过程提供生命周期函数,通过这些过程钩子提供插件注册的机制,然后通过各种 loader 和 plugin 提供了功能强大的功能,在每个阶段调用插件的方法对文件内容进行解析,处理后输出。
其中 js 文件的处理默认是交由 babel-loader 的,核心也就是调用 babel 转换文件成标准的模块,所以无论是 ES7,ES6都是统一调用 babel 进行解析,从入口文件开始分析依赖文件,然后把 js 文件按照依赖一个一个的解析生成标准的 ES5 模块,而 webpack 所做的简单工作就是把生成的这些模块组合成 bundle.js 文件,最后提供了一个 require 加载器然后就可以在浏览器中运行了。(实际 webpack 做的工作远远复杂)
所以说白了,这是 Babel 实现的转换过程,将 ES6 的规范转换成 ES5 也支持的 Commonjs 的过程而已,剩下的就是让 webpack 处理了,而 webpack 简单粗暴的把这些依赖全都打包到一个文件,解决了客户端怎么处理异步的问题,那就是不要异步,Common.js 也就能正常运行了。直接后果就是打包后动辄数兆的巨大 js 体积,这也是 webpack 刚开始被诟病很多的一点,反前端的东西,就像是一个大胖子被嫌弃到委屈想哭的样子。不过这种简单粗暴的开发方式深得广大底层民工的认同,不在纠结异步加载和界面的等待等包袱,优秀的开发体验,更加方便的在客户端组织大型 APP 的开发,已经足够证明这种开发方式有存在的必要了,更不用说后面 webpack 针对一些问题进行的改进了。
回过头看这个问题,下面这个链接是 babel 的在线转换,可以在左边编写不同规范的 js,右边会转成类似在 webpack 中生成代码的模块结构,可以看到 import 被转成了 require,babel 尽职的把 ES6 转成了 ES5,仿佛还嘟哝了一句又不是不能用。
总结下就是说并不是 webpack 支持的这几种加载模块都可以接受,都能够兼容,而是由于 Babel 在从 ES6 转到 ES5 的过程中,使用语法转换的方式,再加上辅助函数的支持,从中间层兼容了多种写法。
很多规范和标准现在并不是过时或者不能用,都是十分优秀的解决方案,都有十分具体适用的使用场景。
本来写了很多关于各个标准的演进和实现,但是回想了一下好像不是想说这些,而且这些随便找个博客也都能说的更清楚。所以先说下几个标准是怎样的。
define(["a", "b"], function(a, b) {b.foo()});
一般使用 require.js 这个库来处理
define(function(require, exports, module) {var a = require('./a'); //在需要时申明a.method_1();exports.add = function() {}});
一般使用 sea.js 这个库来处理
AMD, CMD 是用闭包的形式隔离模块,同时加上异步处理来适应浏览器端
var math = require('math');exports.result = math.add(2,3);
一般直接在同一个文件中,或者 node 端
import defaultExport from "module-name";import * as name from "module-name";import { export } from "module-name";import { export as alias } from "module-name";import { export1 , export2 } from "module-name";import { export1 , export2 as alias2 , [...] } from "module-name";import defaultExport, { export [ , [...] ] } from "module-name";import defaultExport, * as name from "module-name";import "module-name";export { name1, name2, …, nameN };export { variable1 as name1, variable2 as name2, …, nameN };export let name1, name2, …, nameN; // also varexport let name1 = …, name2 = …, …, nameN; // also var, constexport function FunctionName() {...}export class ClassName {...}export default expression;export default function (…) { … } // also class, function*export default function name1(…) { … } // also class, function*export { name1 as default, … };export * from …;export { name1, name2, …, nameN } from …;export { import1 as name1, import2 as name2, …, nameN } from …;
ECMAScript2015 规定了 Javascript 的标准,叉着腰等着别人改成自己的规范。
现在大部分都是预编译工具,所以常常打交道的是
Commonjs
和 esModule
以上能看到前端的很多工程化方案多么受到浏览器这样的网络协议驱动应用的限制
其实最重要的想说,写代码的时候不能不知其所以然,在现在工程化程度特别高的时代,很多配置都有十分成熟的脚手架工具,一行代码开箱即用的体验。但是这些东西都是辅助的工具箱,我们即将秃顶的艺术家更需要注重基础,不然很多时候项目报错都会让自己不知所措,或者会出现“明明自己弄得没错怎么还是没法运行,一阵鼓捣后有莫名其妙成功运行的奇妙过程”。而应接不暇的工具和框架就像是时代的洪流,你唯一能追的上的是自己解决问题的能力。
感谢您的阅读,本文由 Ubug 版权所有。如若转载,请注明出处:Ubug(https://ubug.io/blog/import-or-require)