乐趣区

关于前端:前端工程化4如何去做js模块化开发ES-ModulesCommonJS有什么区别

1. 如何去做 js 模块化开发 => 模块化规范 + 加载器

1.1、咱们说探讨的仅限于 javascript 代码的模块化,如果要波及到所有文件的模块化请应用 webpack。
1.2、那么 js 的模块化能够用一句话概括:模块化规范 + 加载器;本文次要介绍模块化规范。

2. 几种模块化规范比照:

CommonJS

1、以同步的模式加载模块:通常在 Nodejs 环境应用,不适宜浏览器

1. 因为服务器读本地磁盘文件会比拟快,所以 nodejs 的执行机制是在启动的时候加载所有模块,不须要在执行过程中才加载模块;2. 如果在浏览器端代码执行时去同步 require 很多模块,也会影响页面执行效率。

2、一个文件就是一个模块,且每个模块都有独自的作用域;因为模块输入的是一个值的浅拷贝。
3、require(模块) 加载的是一个对象,该对象是运行时生成;
4、导入导出

// 导出
module.exports = {
    name: 'wangyi',
    age: 18
}

// 导入
const {name, age} = require('./module.js')

ES Modules

1、以异步的模式加载模块:反对 Nodejs 环境应用,也适宜浏览器(ES6 以上才反对的标准,存在兼容性问题,最好配合 webpack 进行加载);
2、ES Modules 的导入导出是固定用法,输入的是值的只读援用(原始值变了,取值跟着变);
3、ES Modules 的导入导出不是对象而是对外接口,该接口在代码编译时就实现,执行效率更高
4、导入导出

// 留神:此处是固定用法,export 前面不是对象;export default 前面才是对象

// 导出 1
export {name, age}
// 导入 1
import {name, age} from './module.js'

// 导出 2
export default {name, age}
// 导入 2
import module from './module.js'
const {name, age} = module

AMD + require.js

1、以异步的形式加载模块,能够指定回调函数;
2、AMD 标准配合 require.js 库作为加载器应用;
3、目前绝大多数第三方库都反对 AMD
4、应用起来比较复杂
5、模块 js 文件申请频繁,因为每个模块都会创立一个 script 标签去申请文件
6、导入导出

define('module1', ['jquery', './module2'], function($, module2){
  return {// 能够在外面应用依赖的 $、module2 模块}
})

CMD + sea.js

CMD 标准配合 sea.js 库作为加载器应用,实现了模块化开发(淘宝);起初 sea.js 被 require.js 兼容了,便不再应用。

3. 模块化规范应用最佳实际

3.1、Nodejs 环境 => CommonJS

3.1.1、因为服务器读本地磁盘文件会比拟快,所以 nodejs 的执行机制是在启动的时候加载所有模块,不须要在执行过程中才加载模块;
3.1.2、如果在浏览器端代码执行时去同步 require 很多模块,也会影响页面执行效率。
3.1.3、module 对象是在 Nodejs 环境定义的,配合 require 函数应用;如果说谁是 CommonJS 的加载器,那就是 Nodejs 环境。

3.2、浏览器端 => ES Modules + Webpack

3.2.1、因为浏览器端会有很多的异步加载且以后的 ES6 开发比较简单,所以浏览器端适宜应用 ES Modules。
3.2.2、ES Modules 通常通过 webpack + babel 进行转换;将其转换成立刻执行函数的形式,以此来模拟块级作用域;(webpack 也反对在源码中应用 CommonJS 和 ESM 相互导入导出,但个别不必)
3.2.3、因为 webpack 是在 nodejs 环境运行,所以其配置文件通常应用 CommonJS 标准。
3.2.4、因为 ES Modules 通常须要配合打包工具进行应用,所以 webpack 能够算得上它的加载器。

4. ES Module 根本应用知识点

4.1、ES Modules 尽管是 ES6 才呈现的标准,然而将来浏览器原生反对

4.2、ES Modules 反对在 script 标签上间接定义应用:
4.2.1. ESM 主动采纳严格模式,疏忽 ‘use strict’
4.2.2. 每个 ES Module 都是运行在独自的公有作用域中
4.2.3. ESM 是通过 CORS 的形式申请内部 JS 模块的
4.2.4. ESM 的 script 标签会提早执行脚本,和 defer 一样的成果

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>ES Module - 模块的个性 </title>
</head>
<body>
  <!-- 通过给 script 增加 type = module 的属性,就能够以 ES Module 的规范执行其中的 JS 代码了 -->
  <script type="module">
    console.log('this is es module')
  </script>

  <!-- 1. ESM 主动采纳严格模式,疏忽 'use strict' -->
  <script type="module">
    console.log(this)
  </script>

  <!-- 2. 每个 ES Module 都是运行在独自的公有作用域中 -->
  <script type="module">
    var foo = 100
    console.log(foo)
  </script>
  <script type="module">
    console.log(foo)
  </script>

  <!-- 3. ESM 是通过 CORS 的形式申请内部 JS 模块的 -->
  <!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->

  <!-- 4. ESM 的 script 标签会提早执行脚本,和 defer 一样的成果 -->
  <script type="module" src="demo.js"></script>
  <p> 须要显示的内容 </p>
</body>
</html>

4.3、ES Modules 的 export 和 import:

  • 导出:

    • export {} 前面不是一个对象,而是固定用法 { XXX},import {sss} from module 也是固定用法,不是解构语法;export default {} 前面才能够跟一个对象、字符串等都行。
    • export 导出的是一个援用关系,而且是只读的!不是深拷贝的对象。
var name = 'foo module'

function hello () {console.log('hello')
}

class Person {}

export { 
  name as foo, 
  hello, 
  Person 
}
  • 导入(导入门路必须残缺):

    • 文件名称必须残缺,不能省略.js、/index.js;
    • 相对路径也必须残缺,不能省略./;
    • 能够绝对路径或者残缺的 url

import 不反对动静导入,须要应用 import().then()

import {foo, hello, Person} from './module.js'
console.log(name, hello, Person)

// 只加载模块不提取模块变量,能够简写:import './module.js'
import {} from './module.js'
import './module.js'

// 导入模块内的全副变量
import * as mod from './module.js'
console.log(mod)

// 动静导入
import('./module.js').then(function (module) {console.log(module)
})

4.4、ES Modules 的 export default

  • export default 前面能够跟对象、字符串等类型
  • export 过后能够持续增加 export default
  • import 的第一个地位默认对应 export default 导出的值
// module.js
var name = 'jack'
var age = 18

export {name, age}
console.log('module action')
export default 'default export'
// import.js
// import {name, age, default as title} from './module.js'
// abc 为 export default 导出值的重命名,abc 前面的 { } 不是对象解构,而是固定用法
// import abc from './module.js'
import abc, {name, age} from './module.js'
console.log(name, age, abc)

4.5、ES Modules 的浏览器兼容
IE 根本不兼容 ES Modules

插件 browser-es-module-loader 用于兼容 ES Modules(开发阶段可用,不倡议生产环境应用)
1、在 html 中间接应用,参考:npm 官网地址

// 该办法须要动静的去解析脚本执行 ESM,性能差!只能在开发阶段应用。// script 加上 nomodule 属性,防止在反对 ESM 的浏览器上执行两次
// babel-browser-build.js 为 babel 的运行环境(浏览器端)<script nomodule src="dist/babel-browser-build.js"></script>
// ES Modules 把代码读出来交给 babel 转换
<script nomodule src="dist/browser-es-module-loader.js"></script>
 
<!-- script type=module loading -->
<script nomodule  type="module" src="path/to/module.js"></script>

...

2、npm 中应用,预计还是作为依赖资源动静解析 ESM(不倡议应用)

npm install browser-es-module-loader --save-dev

4.6、ES Modules 的 NodeJS 反对状况(8.5+ 版本)
NodeJS 8.5 以上的版本反对 ES Modules,然而还是试验版本;

// 第一,将文件的扩展名由 .js 改为 .mjs;(nodejs 12.10 版本以上不须要批改文件名了)// 第二,启动时须要额定增加 `--experimental-modules` 参数;import {foo, bar} from './module.mjs'

console.log(foo, bar)

// 此时咱们也能够通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')

// 也能够间接提取模块内的成员,内置模块兼容了 ESM 的提取成员形式
import {writeFileSync} from 'fs'
writeFileSync('./bar.txt', 'es module working')

// 对于第三方的 NPM 模块也能够通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')

// 不反对,因为第三方模块都是导出默认成员
// import {camelCase} from 'lodash'
// console.log(camelCase('ES Module'))

4.7、ES Modules 和 CommonJS 互相应用(在 NodeJS 环境中)

  • ESM 中可用导入 CommonJS
  • CommonJS 中不能导入 ESM
  • CommonJS 始终只会导出一个默认成员
  • import 不是解构导出对象,只能:import mod from ‘./commonjs.js’

4.8、ES Modules 和 CommonJS 的差别(在 NodeJS 环境中)

// nodejs、CommonJS,文件名为:mjs
// 加载模块函数
console.log(require)

// 模块对象
console.log(module)

// 导出对象别名
console.log(exports)

// 以后文件的绝对路径
console.log(__filename)

// 以后文件所在目录
console.log(__dirname)
// nodejs、ES Modules,文件名为:mjs
// require, module, exports 天然是通过 import 和 export 代替

// __filename 和 __dirname 通过 import 对象的 meta 属性获取
// const currentUrl = import.meta.url
// console.log(currentUrl)

// 通过 url 模块的 fileURLToPath 办法转换为门路
import {fileURLToPath} from 'url'
import {dirname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)

5. Webpack 基于所有资源去做模块化

残缺的解说参考后续文章:待续;

5.1、反对新个性语言版本的编译
5.2、针对 javascript 模块化打包
5.3、针对所有资源,例如款式、图片、字体等进行模块化

对于 1、2 两点,grunt、gulp 等脚手架能够很好的解决,然而无奈解决第 3 点。

6、Rollup:专门针对 ES Modules 进行打包的轻量化工具

  • webpack 大而全 => 适宜做大型应用程序
  • rollup 小而美 => 适宜做类库

7、Parcel:零配置专用打包器,简略易用

8、参考资料:

ES6- 模块与 -CommonJS- 模块的差别

特地鸣谢:拉勾教育前端高薪训练营

退出移动版