聊一聊前端的模块化

802 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与 「掘力星计划」  ,赢取创作大礼包,挑战创作激励金

背景

  • 前端模块与时代息息相关,很久以前, JS 并没有模块体系,也就意味着无法将大的程序拆分为相互依赖的小文件、再拼装起来。

  • ES6(ES2015)之前主要使用的模块规范

    • CommonJS 用于服务器,特点是同步加载,缺点是前端不友好,同步意味着阻塞
    • AMD、CMD用于浏览器,AMD开发成本较高、代码阅读困难,CMD模块的加载逻辑偏重
  • ES6以后

    • 模块思想:尽量静态化,使得编译时就能够确定模块的依赖关系、输出和出入的变量;CommonJS 和 AMD 模块,都只能在运行时确定这些。(比如:CommonJS 模块就是对象,输入时必须查找对象属性)
    • ES6 在语言标准的层面上,实现了模块功能。但截止目前,各类浏览器引擎还未完全实现 ES6,现阶段需借助BABEL工具、转换为 ES5 再执行,这就导致 import 与 module.exports,require 与 export 这些在我们开发时候是可以混用的
  • 本文主要探讨,混用时应该注意些什么?服务端应该如何使用?纯前端JS模块如何混用(借助webpack-build)?

问题:文末解答

  • 为什么 React、Vue、element-ui 的 export default 可以直接在 import 时解构?
  • 前端开发ES6语法标准下, 为什么能混用 require?module.exports? 引入的时候注意些什么?
  • node 和 es6 关系?
  • webpack 的 tree-shaking 干啥的?
  • app-api 中 es6/index.js 的 require 用法是否可以用 import 替代?为什么?
  • 怎么开发一个 Node 和 ES6 环境都支持的npm包?

模块规范

CommonJS模块规范

  • 每个文件就是一个模块,有自己的作用域,文件内定义的变量、函数、类都是私有的,对其他文件不可见
  • Node 遵循 CommonJS 模块规范
    • Node为每个模块提供一个exports变量 (对外的接口),指向module.exports
// 每个模块头部,有一行这样的命令
var exports = module.exports;
  • module的属性如下
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。
  • 注意:不能将exports变量指向其他值

  • 加载模块:require,其实是加载模块的module.exports属性

    • require 加载机制:输入的是被输出的值的拷贝,一旦模块输出一个值,模块内部的变化不会影响到已经输出的值
    • TODO require 内部处理

ES6模块规范

  • default是 ES6 独有的关键字
  • export default 为模块指定默认输出,只能使用一次
  • export const xxx 单独输出,可多次使用

常见组合方式

import

  • import 与 export const xxx
//export.js
export const obj = { name: 'module'};
 
//index.js
import { obj } from './export';
console.log(obj.name); //module
 
import * as customObj from './export';
console.log(customObj.name); //module
  • import 与 export default
//export.js
export default { name: 'module'};
 
//index.js
import obj from './export';
console.log(obj.name); //module
//错误写法
import { name } from './export';
  • import 与 module.exports
//export.js
module.exports = { name: 'module' };
 
//index.js
import { name } from './export';
console.log(name); //module
 
import customObj from './export';
console.log(customObj.name); //module

require

  • require 与 module.exports
//export.js
mocule.exports = { name: 'module'};
 
//index.js
const exportsObj = require("./export");
console.log(exportsObj.name); //module
  • require 与 export const xxx
//export.js
export const obj = { name: 'module' };
 
//index.js
const { exportObj }| = require("./export");
console.log(exportObj.name); //module
  • require 与 export defalut
//export.js
export default { name: 'module' };
 
//index.js
const { exportObj }| = require("./export").default;
console.log(exportObj.name); //module

TODO Webpack模块化解析

webpack 和 babel 的原理 webpack 混合打包的情况

错误的用法

  • 语法错误
//exoprt.js
export 1;
//export.js
const obj = { name: 'module' };
export obj;
 
// 以上错误原因:没有对外提供接口,直接输出了值,不是接口
// 导出的本质是:接口名和比内部变量需要建立一对一的关系
//export.js
export default const obj = { name: 'module' };
 
// 以上错误原因:试图重新定义default变量
  • 同一个模块中
//export.js
import { a } from './export.js';
module.exports = {
    name: a
}
 
//export.js
// 切断了 exports 与 module.exports 的联系
exports = (param) => { //... }
 
// 因为 module.exports 被重新赋值,sayHello 无法对外输出
exports.sayHello = function() {
  return 'hello'
}
 
module.exports = 'Hello world'

#不建议的用法

  • 纯前端模块文件中出现 module.exports 导出
  • node应用的模块中单独对exports赋值或修改其指向

答案

  • 这些开源包在打包后,大多数打成符合Commonejs规范的包、或者UMD类型的包
  • 纯前端工程中使用ES6为模块标准,是webpack帮我们实现了require
  • NodeJS 9.0+ 版本支持ES6模块标准
  • tree-shaking 自动检测、静态分析没有使用的代码模块,前提是必须使用ES6标准模块
  • 可以代替,app-api中环境变量属于浏览器端运行时判断,不会存在tree-shaking优化、模块都会打包输出到浏览器端
  • webpack导出指定 Library 和 libraryTarget

综上

  • 开发时候要分清楚运行环境
  • 不要随意混用CommoneJs 和 ES6
  • 开发多端支持的npm包时请先进行模块设计

点赞支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。

11.png

往期精彩推荐

前端常用的几种加密方法

canvas 爬坑路【方法篇】

不懂 seo 优化?一篇文章帮你了解如何去做 seo 优化

canvas 爬坑路【属性篇】

【实战篇】微信小程序开发指南和优化实践

聊一聊移动端适配

前端性能优化实战

聊聊让人头疼的正则表达式

获取文件blob流地址实现下载功能

Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM

Git 相关推荐

通俗易懂的 Git 入门

git 实现自动推送

面试相关推荐

前端万字面经——基础篇

前端万字面积——进阶篇

更多精彩详见:个人主页