Navigation
阅读进度0%
No headings found.

模块化开发与ES Modules详解

December 19, 2024 (1y ago)

JavaScript
ES Modules
CommonJS
AMD
Node.js

1、概述

模块化的开发方式是一种代码管理方式的提现,用约定用语法 去约定复杂代码的管理方式,方便开发大型应用,方便 对大型项目的维护

我们开看一下 模块化,在前端的发展历史

- [x] 阶段1  直接script脚本导入的方式

缺点:污染全局,命名冲突,无法管理模块之间的互相依赖

- [x] 阶段2 解决全局命名的问题

缺点: 模块里的成员 在外部依然能被修改,而且模块依赖的我那天也没有得到很好的解决

- [x] 阶段3 立即执行函数!

缺点:基本上解决了上面的问题,但是还是有很多问题没有解决

上述的方案都是script的方式载入的,对于页面来说不好 模块的加载不受 代码的控制,比如你忘记加什么模块了什么的,如何解决上面的问题呢,“模块规范标准 + 模块预加载器”就是我们实现的一个思考方向。我们先看看 就规范来说,有哪些

- [x] Commonjs规范。

commonjs规范是nodejs中的,它的特点就是

  1. 一个文件就是一个模块
  2. 每个模块都有单独的作用域
  3. 通过module.export 导出成员
  4. 通过 require函数在载入模块

但是由于它是同步的,会堵塞浏览器,的执行,所以我们 由有了再浏览器环境下的AMD

- [x] AMD 规范 异步的模块定义

它竟是一个规范,又是一个模块加载器,具体的语法可以看这张图

图上的是定义模块

这个规范的内部就是创建一个scirpt标签去 发请求拿数据

缺点就是: 语法复杂,如果模块过多,同一个页面就会爆发很多请求 ,不友好

以上就是我们的前期,现在我们的模块化,在js的语法层面已经非常的统一了,在node环境下用Commonjs规范,在浏览器环境下使用 ES Module 规范,node下的Commonjs规范没什么要说的,要说的就是ES Module 它在不同环境下表现不太一致, 也有兼容问题,但是却是语法层面的规范,所以我们需要重点掌握它

3、ES Modules特性

我们需要了解他的特性,以便于我们后续聊聊他的兼容性和对应的解决方案

  • 使用上的一些注意事项
<!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> -->
 
  // 浏览器运行加载页面的时候,会堵塞html的渲染,它在加载时候是立即执行的,
  <!-- 4. ESM 的 script 标签会延迟执行脚本 defer 与 type="module" -->
  <script defer src="demo.js"></script>
  <p>需要显示的内容</p>
</body>
</html>
 

demo.js

alert('hello ')

4、ES Modules 导出

我们来看看文件夹的构建方式吗,这一讲我们主要是来看看 导入和导出的语法,主要的语法就是import 和export ,在模块内都是局部作用域,外部如果不用export 是拿不到的

app.js

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

module.js

// export var name = 'foo module'  导出变量
 
// export function hello () {
//   console.log('hello')
// } 导出函数
 
// export class Person {}
 
var name = 'foo module'
 
function hello () {
  console.log('hello')
}
 
class Person {}
 
// export { name, hello, Person } 统一导出
 
// export {  as 重命名
//   // name as default,
//   hello as fooHello
// }
 
// export default name  这是一个固定的语法!不是对象什么的语法 { } 就是一个固定的语法
export 只允许有两个方式
export default xxx
export { xxx }
 
// var obj = { name, hello, Person }
 
export { name, hello, Person }
	

5、ES Modules 导入导出的注意点

**注意!,我们的的import 不是结构!!!,**我们的导出不是一个对象,而是引用

import 导入的模块的时候,不能省去js扩展名,但是一般来说,我们开发的时候可以不写 是因为通过babel进行了转换 我们有下面的语法细节

app.js

// import { name } from './module'
// import { name } from './module.js'
// console.log(name)
 
// import { lowercase } from './utils'
// import { lowercase } from './utils/index.js'
// console.log(lowercase('HHH'))
 
// import { name } from 'module.js'
// import { name } from './module.js'
// import { name } from '/04-import/module.js'
// import { name } from 'http://localhost:3000/04-import/module.js'
// console.log(name)
 
// --------------
 
// import {} from './module.js'
// import './module.js'
 
// ---------------
 
// import * as mod from './module.js'
// console.log(mod)
 
// ---------------
 
// var modulePath = './module.js'
// import { name } from modulePath
// console.log(name)
 
// if (true) {
//   import { name } from './module.js'
// }
 
// import('./module.js').then(function (module) {
//   console.log(module)
// })  动态路径导入
 
// ----------------
 
// import { name, age, default as title } from './module.js'
import abc, { name, age } from './module.js'
console.log(name, age, abc)
 

moudle.js

var name = 'jack'
var age = 18
 
export { name, age }
 
console.log('module action')
 
export default 'default export'
 

utils/index.js

export function lowercase (input) {
  return input.toLowerCase()
}
 

一般的我们如果需要 导入导出一般是这样做的

8、ES Modules 浏览器环境兼容 Polyfill

我们只需要使用polyfill就好了,非常的简单

<!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 浏览器环境 Polyfill</title>
</head>
<body>
    //nomodule 表示这个脚本只会在不支持 esmodule下去执行脚本,这个东西哈,都是执行阶段去装换,我们不要用到项目上去
    <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>   [promise 兼用处理]
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>  [babel核心]
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script> [es-module插件]
  <script type="module">
    import { foo } from './module.js'
    console.log(foo)
  </script>
</body>
</html>
 

9、ES Modules in Nodejs

我们需要看看在node下如何使用 es modules

1、小demo

前置条件

# node version 要大于10
# 修改js的名称 变成 mjs
# 运行的时候需要调整一下运行时配置
node --expermental-modules index.mjs
 
 

app.mjs

// 第一,将文件的扩展名由 .js 改为 .mjs;
// 第二,启动时需要额外添加 `--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'))
 

module.mjs

export const foo = 'hello'
 
export const bar = 'world'
 

2. ES Modules in Nodejs + Commonjs

我们来看看如何在es和commonjs一起混合使用

commonjs,js

// CommonJS 模块始终只会导出一个默认成员
 
// module.exports = {
//   foo: 'commonjs exports value'
// }
 
// exports.foo = 'commonjs exports value'
 
// 不能在 CommonJS 模块中通过 require 载入 ES Module
 
// const mod = require('./es-module.mjs')
// console.log(mod)
 

es-module.js

// ES Module 中可以导入 CommonJS 模块
 
// import mod from './commonjs.js'
// console.log(mod)
 
// 不能直接提取成员,注意 import 不是解构导出对象
 
// import { foo } from './commonjs.js'
// console.log(foo)
 
// export const foo = 'es module export value'
 

3. es 和cjs的差异

cjs

// 加载模块函数
console.log(require)
 
// 模块对象
console.log(module)
 
// 导出对象别名
console.log(exports)
 
// 当前文件的绝对路径
console.log(__filename)
 
// 当前文件所在目录
console.log(__dirname)
 

esjs

// ESM 中没有模块全局成员了
 
// // 加载模块函数
// console.log(require)
 
// // 模块对象
// console.log(module)
 
// // 导出对象别名
// console.log(exports)
 
// // 当前文件的绝对路径
// console.log(__filename)
 
// // 当前文件所在目录
// console.log(__dirname)  以上的东西就是加载不到的了 因为不存在全局成员 如何解决呢?具名的导入就好了
 
// -------------
 
// 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)
 

11、ES Modules in Nodejs + 新版本进一步讲解

哪有什么新特性,实际上就是一个-配置项在pacjson中,你直接写js就好了如果你还需使用commonjs句需要 .cjs

{
  "type": "module"
}
 

12、ES Modules in Nodejs + Babel

实际上就是配置一些插件,我们先来梳理一些 项目结构 babel是基于插件实现的,现在我们就来配置一些他们

配置好依赖项

    "@babel/core": "^7.6.0",
    "@babel/node": "^7.6.1",
    "@babel/plugin-transform-modules-commonjs": "^7.6.0"

.babelrc

{
  "plugins": [
    "@babel/plugin-transform-modules-commonjs"
  ]
}
 

.如何运行它?

yarn babel-node xxxx.js