前端模块化杂记

前言 CMD\AMD简介 Commonjs简介 Module简介 Common和Module的区别 Module与webpack Module与Babel 一些问题 总结 引用 前言 前端模块化在近几年层出不穷,有Node的CommonJs,也有属于client端的CMD/AMD模式,而ES6本身也出现了Modules,再加上Webpack以及babel的普及,虽然在代码中经常使用到这些用法,但是如果不去深入研究,总觉得是一个黑魔法,无法探测一些问题的根源。 AMD/CMD简介 事实上,随着打包工具和Babel在前端工程化的世界里大放异彩,AMD/CMD也在逐步退出历史的舞台,这里简单的介绍下其用法及语义。 AMD及其用法 AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。代表(require.js) /** main.js 入口文件/主模块 **/ // 首先用config()指定各模块路径和引用名 require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //实际路径为js/lib/jquery.min.js "underscore": "underscore.min", } }); // 执行基本操作 require(["jquery","underscore"],function($,_){ // some code here }); CMD及其用法 CMD 即Common Module Definition, 中文名是通用模块定义的意思。代表(Sea.js) /** sea.js **/ // 定义模块 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; }); // 加载模块 seajs.use(['math.js'], function(math){ var sum = math.add(1+2); }); 两者的区别 1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块 2、CMD推崇就近依赖,只有在用到某个模块的时候再去require Commonjs简介 Commonjs的应用主要是在Node应用中。 通过require引入文件, 文件内部则通过module.export暴露,如下a 就是 module.export // 引入某个文件 const a = require('some.js') // some.js module.export = { ... // some code } 除去module.export,Commonjs还有一个exports属性(不推荐使用), 事实上exports就是module.export // 对外输出接口可以添加变量 var exports = module.exports; exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; }; // 注意不要直接对exports赋值,这样会切断exports和module的关系 exports = a // 不要这么做 Module简介 ES6的Module是官方正式推出的模块化写法,虽然目前有挺多浏览器还不支持,不过我们可以利用babel将其转换,话不多说,先介绍下Module的基本用法。 ES6的module主要是以import导入想要的对象,export 和 export default导出对象 import x from 'some.js' // 引用some.js中的export default import {a, b} from 'some.js' // 引用some.js的 export a 和 export b import x, {a, b} from 'some.js' // 引用 some.js的 export default 和 export a 和 export b // some.js const x = () => {} export const a = () => {} export const b = () => {} export default x 因为import是编译时加载,所以import命令具有提升效果,会提升到整个模块的头部,首先执行。 // some code ... ... import xxx from 'xxx' // 提升到最顶部 Common和Module的区别 1. 加载的时机不同 Common是运行时加载的,可以使用变量或者表达式,如: const 'f' + 'oo' = require('my_modules') Module是编译时加载的,不可以使用变量或者表达式, 编译时加载效率较高。 2.暴露出的接口不同 Common暴露出来的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 // lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, }; // main.js var counter = require('./lib').counter; var incCounter = require('./lib').incCounter; console.log(counter); // 3 incCounter(); console.log(counter); // 3 Module则相反, 输出的是值的引用。 Module与webpack webpack本身维护了一套模块系统,这套模块系统兼容了所有前端历史进程下的模块规范,包括 amd commonjs es6 等,为了看module在webpack中是怎么运行的,我们可以看一下下面简单的代码: // webpack const path = require('path'); module.exports = { entry: './a.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', } }; // a.js import a from './c'; export default 'a.js'; console.log(a); // c.js export default 333; 打包后的代码如下: (function(modules) { function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; } return __webpack_require__(0); })([ (function (module, __webpack_exports__, __webpack_require__) { // 引用 模块 1 "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); }), (function (module, __webpack_exports__, __webpack_require__) { // 输出本模块的数据 "use strict"; /* harmony default export */ __webpack_exports__["a"] = (333); }) ]); 简化一波代码再看,可以看出打包后实际上是一个立即执行函数,并且入参为各个module文件, 最后返回的**是__webpack_require__(0)**: (function(modules) { function __webpack_require__(moduleId) { } return __webpack_require__(0); })([module1, module2]); ok, 我们继续看__webpack_require__函数,可以看出它是调用了我们的入口模块,同时传入了module相关的属性,以及函数本身 function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; } 那么继续追溯到入口模块,也就是我们的第一个参数我们可以看到入口模块又调用了 webpack_require(1) 去引用入参数组里的第2个函数。 然后会将入参的 webpack_exports 对象添加 default 属性,并赋值。 这里我们就能看到模块化的实现原理,这里的 webpack_exports 就是这个模块的 module.exports 通过对象的引用传参,间接的给 module.exports 添加属性。 最后会将 module.exports return 出来。就完成了 webpack_require 函数的使命。 function (module, __webpack_exports__, __webpack_require__) { /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); } 至此,我们可以看出module其实在webpack中,最后的打包结果。 Module与Babel 虽然webpack可以打包转换我们的module,但通常我们都会引入babel来对ES6转成ES5的代码,而Moduel属于ES6,也会被转译。 事实上,babel是将module转换成commonjs,这样 webpack 就无需再做处理,直接使用 webpack 运行时定义的 webpack_require 处理。 不过babel在转换的时候,会有一些特殊的处理, 像下面 首先 export 的时候, 会添加一个__esModule属性到exports,是为了表明这是经过转换的module export default a // 转换成 Object.defineProperty(exports, "__esModule", { value: true }); exports.default = a; 再看 转出的 转出其实会多一个_interopRequireDefault函数,就是为了处理default这个属性 import d from 'd' // 转化后 var _d = require('d'); var _d2 = _interopRequireDefault(_d); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 一些问题 1.为什么有的地方使用 require 去引用一个模块时需要加上 default? 我们在上文 babel 对导出模块的转换提到,es6 的 export default 都会被转换成 exports.default,即使这个模块只有这一个输出。 2.经常在各大UI组件引用的文档上会看到说明 import { button } from 'xx-ui' 这样会引入所有组件内容,需要添加额外的 babel 配置,比如 babel-plugin-component? import { Button, Select } from 'element-ui' // 转换成 var a = require('element-ui'); var Button = a.Button; var Select = a.Select; babel-plugin-component就做了一件事,将 import { Button, Select } from 'element-ui' 转换成了 import Button from 'element-ui/lib/button' import Select from 'element-ui/lib/select' 3.我们在浏览一些 npm 下载下来的 UI 组件模块时(比如说 element-ui 的 lib 文件下),看到的都是 webpack 编译好的 js 文件,可以使用 import 或 require 再去引用。但是我们平时编译好的 js 是无法再被其他模块 import 的,这是为什么? 通过 webpack 模块化原理章节给出的 webpack 配置编译后的 js 是无法被其他模块引用的,webpack 提供了 output.libraryTarget 配置指定构建完的 js 的用途。入口模块返回的 module.exports 赋值给 module.exports 总结 在剖析了整体的流程之后,可以看到相关的技术细节还是比较清晰的,学无止境~~~ 引用 import、require、export、module.exports 混合使用详解 Module的语法 前端模块化 Commonjs规范 分类: Js,性能 标签: module, es6, commonjs, webpack, babel, 前端, 模块化前言 CMD\AMD简介 Commonjs简介 Module简介 Common和Module的区别 Module与webpack Module与Babel 一些问题 总结 引用 前言 前端模块化在近几年层出不穷,有Node的CommonJs,也有属于client端的CMD/AMD模式,而ES6本身也出现了Modules,再加上Webpack以及babel的普及,虽然在代码中经常使用到这些用法,但是如果不去深入研究,总觉得是一个黑魔法,无法探测一些问题的根源。 AMD/CMD简介 事实上,随着打包工具和Babel在前端工程化的世界里大放异彩,AMD/CMD也在逐步退出历史的舞台,这里简单的介绍下其用法及语义。 AMD及其用法 AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。代表(require.js) /** main.js 入口文件/主模块 **/ // 首先用config()指定各模块路径和引用名 require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //实际路径为js/lib/jquery.min.js "underscore": "underscore.min", } }); // 执行基本操作 require(["jquery","underscore"],function($,_){ // some code here }); CMD及其用法 CMD 即Common Module Definition, 中文名是通用模块定义的意思。代表(Sea.js) /** sea.js **/ // 定义模块 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; }); // 加载模块 seajs.use(['math.js'], function(math){ var sum = math.add(1+2); }); 两者的区别 1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块 2、CMD推崇就近依赖,只有在用到某个模块的时候再去require Commonjs简介 Commonjs的应用主要是在Node应用中。 通过require引入文件, 文件内部则通过module.export暴露,如下a 就是 module.export // 引入某个文件 const a = require('some.js') // some.js module.export = { ... // some code } 除去module.export,Commonjs还有一个exports属性(不推荐使用), 事实上exports就是module.export // 对外输出接口可以添加变量 var exports = module.exports; exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; }; // 注意不要直接对exports赋值,这样会切断exports和module的关系 exports = a // 不要这么做 Module简介 ES6的Module是官方正式推出的模块化写法,虽然目前有挺多浏览器还不支持,不过我们可以利用babel将其转换,话不多说,先介绍下Module的基本用法。 ES6的module主要是以import导入想要的对象,export 和 export default导出对象 import x from 'some.js' // 引用some.js中的export default import {a, b} from 'some.js' // 引用some.js的 export a 和 export b import x, {a, b} from 'some.js' // 引用 some.js的 export default 和 export a 和 export b // some.js const x = () => {} export const a = () => {} export const b = () => {} export default x 因为import是编译时加载,所以import命令具有提升效果,会提升到整个模块的头部,首先执行。 // some code ... ... import xxx from 'xxx' // 提升到最顶部 Common和Module的区别 1. 加载的时机不同 Common是运行时加载的,可以使用变量或者表达式,如: const 'f' + 'oo' = require('my_modules') Module是编译时加载的,不可以使用变量或者表达式, 编译时加载效率较高。 2.暴露出的接口不同 Common暴露出来的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 // lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, }; // main.js var counter = require('./lib').counter; var incCounter = require('./lib').incCounter; console.log(counter); // 3 incCounter(); console.log(counter); // 3 Module则相反, 输出的是值的引用。 Module与webpack webpack本身维护了一套模块系统,这套模块系统兼容了所有前端历史进程下的模块规范,包括 amd commonjs es6 等,为了看module在webpack中是怎么运行的,我们可以看一下下面简单的代码: // webpack const path = require('path'); module.exports = { entry: './a.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', } }; // a.js import a from './c'; export default 'a.js'; console.log(a); // c.js export default 333; 打包后的代码如下: (function(modules) { function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; } return __webpack_require__(0); })([ (function (module, __webpack_exports__, __webpack_require__) { // 引用 模块 1 "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); }), (function (module, __webpack_exports__, __webpack_require__) { // 输出本模块的数据 "use strict"; /* harmony default export */ __webpack_exports__["a"] = (333); }) ]); 简化一波代码再看,可以看出打包后实际上是一个立即执行函数,并且入参为各个module文件, 最后返回的**是__webpack_require__(0)**: (function(modules) { function __webpack_require__(moduleId) { } return __webpack_require__(0); })([module1, module2]); ok, 我们继续看__webpack_require__函数,可以看出它是调用了我们的入口模块,同时传入了module相关的属性,以及函数本身 function __webpack_require__(moduleId) { var module = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); return module.exports; } 那么继续追溯到入口模块,也就是我们的第一个参数我们可以看到入口模块又调用了 webpack_require(1) 去引用入参数组里的第2个函数。 然后会将入参的 webpack_exports 对象添加 default 属性,并赋值。 这里我们就能看到模块化的实现原理,这里的 webpack_exports 就是这个模块的 module.exports 通过对象的引用传参,间接的给 module.exports 添加属性。 最后会将 module.exports return 出来。就完成了 webpack_require 函数的使命。 function (module, __webpack_exports__, __webpack_require__) { /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1); /* harmony default export */ __webpack_exports__["default"] = ('a.js'); console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]); } 至此,我们可以看出module其实在webpack中,最后的打包结果。 Module与Babel 虽然webpack可以打包转换我们的module,但通常我们都会引入babel来对ES6转成ES5的代码,而Moduel属于ES6,也会被转译。 事实上,babel是将module转换成commonjs,这样 webpack 就无需再做处理,直接使用 webpack 运行时定义的 webpack_require 处理。 不过babel在转换的时候,会有一些特殊的处理, 像下面 首先 export 的时候, 会添加一个__esModule属性到exports,是为了表明这是经过转换的module export default a // 转换成 Object.defineProperty(exports, "__esModule", { value: true }); exports.default = a; 再看 转出的 转出其实会多一个_interopRequireDefault函数,就是为了处理default这个属性 import d from 'd' // 转化后 var _d = require('d'); var _d2 = _interopRequireDefault(_d); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 一些问题 1.为什么有的地方使用 require 去引用一个模块时需要加上 default? 我们在上文 babel 对导出模块的转换提到,es6 的 export default 都会被转换成 exports.default,即使这个模块只有这一个输出。 2.经常在各大UI组件引用的文档上会看到说明 import { button } from 'xx-ui' 这样会引入所有组件内容,需要添加额外的 babel 配置,比如 babel-plugin-component? import { Button, Select } from 'element-ui' // 转换成 var a = require('element-ui'); var Button = a.Button; var Select = a.Select; babel-plugin-component就做了一件事,将 import { Button, Select } from 'element-ui' 转换成了 import Button from 'element-ui/lib/button' import Select from 'element-ui/lib/select' 3.我们在浏览一些 npm 下载下来的 UI 组件模块时(比如说 element-ui 的 lib 文件下),看到的都是 webpack 编译好的 js 文件,可以使用 import 或 require 再去引用。但是我们平时编译好的 js 是无法再被其他模块 import 的,这是为什么? 通过 webpack 模块化原理章节给出的 webpack 配置编译后的 js 是无法被其他模块引用的,webpack 提供了 output.libraryTarget 配置指定构建完的 js 的用途。入口模块返回的 module.exports 赋值给 module.exports 总结 在剖析了整体的流程之后,可以看到相关的技术细节还是比较清晰的,学无止境~~~ 引用 import、requi
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信