js模块发展历程-javaScript模块七日谈
前端模块化开发那点历史 #588现代ES模块也须要各类转码工具才能够在浏览器里正常运行,下面是转码现代ES模块须要了解到的知识点javascript
commonjs & ES module & babel转码 & webpack转码html
CommonJS 模块输出的是一个值的 拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值了 若是输出的是对象,改变其属性的话,外部引用的地方是会发生变化的 若是直接改变输出的引用,那外界引用的地方是不会变化的(取缓存里面的结果) CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成 commonjs 一个模块就是一个文件,require 命令第一次执行加载该脚本 就会执行整个脚本,而后在内存中生成一个对象 { id: '...', // 模块名 exports: {}, // 真实的模块 loaded: true // 是否加载完毕 } 之后再次 require 该模块时,就会去缓存里取该对象的 exports 的属性 不管 require 多少次,模块都只会运行一次,后续加载都是从缓存里面取
module.exports
与 exports
的关系commonjs 规范仅仅定义了 exports module.exports 是 nodejs 对 commonjs 规范的实现 咱们把这种实现称为 commonjs2 https://github.com/webpack/webpack/issues/1114#issuecomment-105509929 exports 只是在初始化对 module.exports 的引用 初始化指向同一片内存空间 模块导出的是 module.exports 若是对 module.exports 从新赋值,exports 上,挂的方法/属性将会失效 require 引入的是 module.exports 导出的东西 为避免混乱/错误,通常导出模块只建议用 module.exports 通常第三方包都用这种方式导出 modules.exports = exports = {}
某个模块出现循环加载,就只输出已经执行的部分,还未执行的部分不会输出
)// 代码以下 // a.js exports.A = '我是a模块'; var b = require('./b.js'); console.log('在 a.js 之中, 输出的 b模块==> ', b.B); exports.A = '我是后期修改过的a模块'; console.log('a.js 执行完毕'); // b.js exports.B = '我是b模块'; var a = require('./a.js'); console.log('在 b.js 之中,输出a模块 ==>', a.A); exports.B = '我是修改后的b模块'; console.log('b.js 执行完毕'); // main.js var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中,输出的 a模块=%j, b模块=%j', a.A, b.B); // 输出结果以下: ➜ webpack-plugin git:(master) ✗ node src/babel/index 在 b.js 之中,输出a模块 ==> 我是a模块 b.js 执行完毕 在 a.js 之中, 输出的 b模块==> 我是修改后的b模块 a.js 执行完毕 在 main.js 之中,输出的 a模块="我是后期修改过的a模块", b模块="我是修改后的b模块" // 执行过程以下: 执行 a.js 遇到 require b.js,暂停 a.js 执行,去执行 b.js b.js 执行到第二行,遇到 require a.js ,从缓存中拿出刚刚 a.js 导出的模块,在 b.js 里面使用 继续执行 b.js 后面的代码 待 b.js 执行完毕后,控制权交还 a.js,继续执行 拿到 b.js 导出的模块,在 a.js 继续使用 ... 直到结束
循环引用注意点
:因为 commonjs
模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是所有代码以后的值,二者可能会有差别,因此输入变量的时候必须很是当心,使用 var a = require('a')
而不是 var a = require('a').foo
export default A // 用户不须要知道导出模块的变量名 import a from 'a.js' // 能够导出多个 export var a = 1 // 这种方式能够直接导出一个表达式 或 var a = 1 export {a} // 必须用花括号包起来 import {a} from 'a.js' // as 关键字重命名模块 export { a as A } // 导入导出合并 export { default as Comps } from '../xxx' 至关于 import Comps from './xx' export { Comps } // 执行 loadsh 模块,但并不输出任何值 import 'lodash'; // 总体加载全部模块,访问时用 circle.xxx 访问 import * as circle from './circle';
简述:
ES6 模块不是对象,而是经过export
命令显式指定输出的代码,再经过import
命令输入,它的接口只是一种静态定义
,在代码静态解析阶段
就会生成。// ES6模块 import { stat, exists, readFile } from 'fs'; 上面代码的实质是从fs模块加载 3 个方法,其余方法不加载。 这种加载称为“编译时加载”或者静态加载, 即 ES6 能够在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。 固然,这也致使了无法引用 ES6 模块自己,由于它不是对象。 因为 ES6 模块是编译时加载,使得静态分析成为可能 import命令具备提高效果,会提高到整个模块的头部,首先执行 import命令是编译阶段执行的,在代码运行以前。 因为 import 是静态执行,因此不能使用表达式和变量(这类只有在运行时才能获得结果的语法结构) 静态加载模块的好处: 1. 再也不须要UMD模块 2. 浏览器API能够用模块格式提供,没必要再作成全局变量,再也不须要全局对象如:Math (能够像Python同样用模块导入)
动态 import
动态import() 是很是有用的。而静态型的 import 是初始化加载依赖项的最优选择, 使用静态 import 更容易从代码静态分析工具和 tree shaking 中受益 import(模块路径) 返回 promise,从 then 的结果里拿到加载的模块 webpack 2.x 以后,有一个魔力注释的功能,会把加载的模块重命名为你注释里的文字
script type="application/javascript" 异步加载: async defer 脚本异步加载,不会阻塞dom结构的解析 async:加载完当即执行,渲染引擎中断,待之脚本执行完继续渲染 defer:加载完会等待页面渲染完毕及页面其余脚本执行完毕才会执行 多个 async 执行没有顺序保证,多个 defer 有顺序保证
script type="module" 浏览器对 type="module" 的处理和 defer 标志一致
ES6 处理“循环加载”与 CommonJS 有本质的不一样。 ES6 模块是动态引用,若是使用import从一个模块加载变量(即import foo from 'foo'), 那些变量不会被缓存,而是成为一个指向被加载模块的引用, 须要开发者本身保证,真正取值的时候可以取到值。 es6 模块会在使用使用时才去加载对应的模块 若是是循环应用,能够将对应的输出改写成函数形式,利用函数的变量提高功能
// 此处是对比 CommonJS 模块时运行时加载 -- 值得拷贝 ES6模块时 编译时 输出接口 -- 值得引用 commonjs 模块只会加载一次,之后在 碰到 require 一样的东西就从缓存里面加载 若是把原模块导出的东西改变,引入模块不会跟着改变,仍是从缓存里面取原来的值 ES6模块的运行机制与CommonJS不同,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。 等到真的须要用到时,再到模块里面去取值 JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。 换句话说,ES6的输入有点像Unix系统的“符号链接”,原始值变了,import输入的值也会跟着变。 ES6 模块是动态引用,而且不会缓存值,模块里面的变量绑定其所在的模块。 commonjs: module.exports = {} exports 运行阶段才加载模块,可使用逻辑语句 模块就是对象加载的就是该对象 加载的是整个模块即将全部的接口都加载进来 输出的是值得拷贝,原模块发生变化不会影响已经加载的 this 指向当前的模块 es6 模块 export 能够输出多个 {} export default 解析阶段肯定对外的接口,解析阶段输出接口,不可使用逻辑语句 加载的模块不是对象 能够单独加载其中的几个模块 静态分析,动态引用输出的是值得引用,原模块变化会影响已加载的模块 this 指向 underfined
Babel 对 ES6 模块转码就是转换成 CommonJS 规范 模块输出语法转换 Babel 对于模块输出的转换,就是把全部输出都赋值到 exports 对象的属性上,并加上 ESModule: true 的标识 表示这个模块是由 ESModule 转换来的 CommonJS 输出 对于解构赋值输入 import {a} from './a.js' 转义为 var _c = require('./a.js') 而后取 _c.a 对于 default import a from './a' import {default as a} from './a' babel转义时的处理,引入了一个 函数 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {'default': obj} } var _a = _interopRequireDefault(require("./a.js")); console.log(_a["default"]); // 意思就是若是不是 esmodule 就为其手动添加个 default 属性,取值时统一取 default
export export.default
导出的模块转换为 exports.xxx 和 exports.default
呢?而不是 module.exports
???我没有找到解释,若是您知道,麻烦给我留言下
module.exports = { entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash:8].js" }, mode: "development" };
webpack --config webpack.config.js --env=dev
输出 main.[hash].js
// 打包后代码简化以下 // 首先是一个 webpack 模块运行时代码 (function(modules) { // webpackBootstrap // 缓存模块 var installedModules = {}; // 函数 __webpack_require__ 参数 模块 id,用于加载和缓存模块 function __webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; } /*** 全部加载的模块都存在于 installedModules 内,其结构为: id: { id, loaded: Boolean // 是否加载过 exports // 模块的导出 } */ // 省略... 定义各类工具函数和变量 // Load entry module and return exports // 加载 entry 模块,并返回其导出,咱们写的模块才会被真正执行 return __webpack_require__(__webpack_require__.s = "./index.js"); })({ "./index.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, "./src/a.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, // ... }) 这个自调用的函数的参数 modules,就是包含全部待加载模块的一个对象 { [id: string]: Function } 异步加载: import ==> webpack.requireEnsure ==> webpackJsonp https://www.njleonzhang.com/2018/12/30/webpack-bundle-1.html
export default
这种导出模块的书写方式后,会将其转换成 exports.default
,这时若是用 require
引入时,须要对其加上 .default
如 require('./a.js').default
这样才能获取 a 模块 export default
导出的 a
import
动态加载的模块也须要 .default
才能获取真实模块导出的值,如 import('./a.js').then(res => res.dafault)
须要 babel-plugin-component
前端
import {Button} from 'antdesign' // 会被转换成 commonjs ,以下 var Button = requre('antdesign').Button // 这样就所有加载了 // 此时就须要 babel-plugin-component 出场了,其会将上述组件转换成以下 import Button from 'antdesign/compoents/button' // 继而转换成 commonjs 时会变成这样 var Button = require('antdesign/compoents/button').default 因此通常 ui 框架的项目结构目录向下面同样 compoennts comps1 comps.js index.js ...