模块化开发是一种思想 是一种代码组织方式
本记内容:
|演变 |模块化规范
|常用打包工具 |举例:基于模块化工具构建的现代web 应用
|打包工具的优化技巧演变
演变
1. 基于文件划分(原始方式): 功能/模块数据 - 单独存放到一个文件中 - <script> 引入
缺陷: 全局工作|命名冲突|依赖关系管理问题
2. 命名空间方式(基于第一中) : 每一个模块 只 暴露 一个全局对象 - <script> 引入
解决了:命名冲突问题
未解决:全局工作|依赖关系管理
3. 立即执行函数(IIFE): 将数据放入函数私有作用域中,需要全局的则挂在全局
window.moduleA = { .... }
解决了: 全局工作/私有空间
未解决: 依赖关系管理问题
模块化规范
最佳实践: Node.js = Common JS
Browsers = ES Modules (ES6 定义的模块系统)|语言层面
1. Common JS 规范:一个文件一个模块一个作用域 |使用: module.exports + require
同步加载模式 | node 环境ok:node 启动时加载所有模块,需要时调用
浏览器 问题: 页面加载出现大量同步请求出现 => 效率低下
2. AMD|异步模块定义:为浏览器设计的规范 | 社区提出的规范
约定:每一个模块都通过 define 定义 |require 加载模块
弱点:使用相对复杂|模块JS文件请求频繁
代表:require.js (实现了AMD + 模块加载器)
使用:define(参数1 字符:模块名字,
参数2 数组: 声明模块依赖 ,
参数3 函数: 其参数与参数2对应:
为当先模块提供私有空间
return 向外部导出成员 )
require(数组:依赖,函数)
3. ES Module
内容:特性 和 语法 | 兼容性问题
基本特性: <script type="module'></script>
1. 自动采用严格模式 => 全局模式下 this is undefined | 忽略'use strict'
2. 每一个module 都有独立的作用域
3. CORS 方式请求 外部模块 |需要外部服务器响应头中提供CORS header
3a. CORS 不支持文件方式访问, 必须以http server 访问
4. 延迟执行 script, 相当于 <script defer></script> | 等待渲染完成加载script
|注:网页默认是对script 立即执行的|
导出 和 导入 :
const foo = 'content'
const bName = 'export default'
export { foo } // 导出
| export 可以修饰变量 |也可以修饰函数
|export { name1, name2 ... } 底部集中导出, 常见方法
|导出 成员重命名: export {
name1 as fooName1 // import 使用fooName1
name2 as default
//特殊情况:重命名为defualt时 导入需要重命名
}
|export default bName // 将bName 作为默认导出 // export default <变量|值>
|注意点: export { foo, bar } // 不是字面量,而是固定语法 export { }
export default { foo, bar } // 导出一个对象,foo bar 为keys
import { foo } from './module.js' // 导入
import { default as aName } from './module.js' // default 导入需要重命名
import someName from './module.js' // 直接使用变量名导入默认成员
| import 的 变量 是只读
|import { name } from './module.js' // 相对路径 .js 不能省略, 必须提供完整路径
// 否者:ESM 会 加加载第三方模块
| import { name } from '/src/module.js' // 绝对路径 |从网站根目录开始
| import { name } from 'http://localhost3000/src/module.js' // url 地址
|import { } from './module.js' // 只执行 不提取 === import './module.js'
|import * as mod from './module.js' // 全部提取放入对象中,成员为mod的值
|import('./module.js').then( function( module) { .... } ) // 动态加载模块,
// 通过参数获取成员
|import { name, age, default as title } from './module.js' // 传入多个
| import title,{ name, age } from './module.js' // 简写上一行
| export { name } from './module.js' // 直接导出导入结果
import { name } from './components.js'
export { name }
4. ES Module in Browsers
polyfill 兼容方案 | ES Module 2014 出生|有些浏览器不支持
browser-es-module-loader
通过unpkg获取地址:unpkg.com/browser-es-module-loader
https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/
promise-polfill: https://unpkg.com/promise-polyfill
<script nomodule></script> 只在不支持ESM的浏览器工作|动态解析脚本|生产模式❌
4. ES Module in Node.js
1. 文件名: .js => .mjs
2. 全部引用:
import fs from 'fs' // working | 内置
import _ from 'lodash' // working | 第三方
直接引用成员:
import { writeFileSync } from 'fs' // working |内置|已兼容
import { camelCase } from 'lodash' // not working |第三方|未兼容
5. Common JS 和 ESM 交互
1. ESM 可以直接导入 CJS 模块
2. ESM 不能直接导入CJS 模块成员: import { } 不是解构
3. Node 环境中:CJS 模块 不能 导入 ESM 模块
6.Common JS 中模块成员 在 ESM 中不可用
require | module| exports 使用 import export 代替
__filename | __dirname : import.meta.url (代码段)
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
import { dirname } from 'path'
const __dirname = dirname(__filename)
7. 新版本node中 ESM 支持
1. package.json 中 { "type": "module"} => 主 ESM 辅 CJS
2. 则 CJS 书写的js文件 改名为 .cjs
8. Node.js Bable 兼容方案
低版本node 中使用 ESM
使用bable 运行node: @babel/node 它需要依赖 @babel/core 和 @babel/prset-env
yarn add @babel/node @babel/core @babel/preset-env
yarn add@babel/plugin-transform-modules-commonjs@7.6.0 --dev
"@babel/core": "^7.6.0",
"@babel/node": "^7.6.1",
"@babel/plugin-transform-modules-commonjs": "^7.6.0"yarn babel-node index.js //运行文件
yarn babel-node index.js --presets=@babel/presets-env // 添加preset 编译
为babel 添加配置文件 转换 ESM
// .babelrc
{
"presets":["@babel/preset-env"]
}
// .babelrc
{
plugins: ["@babel/plugin-transform-modules-commonjs"]
}
// node
yarn babel-node index.js // 添加配置文件后可以直接运行文件