2、前端模块化之ES Module及包管理工具的使用学习

72 阅读9分钟

一. ES Module

1.1. ES Module基本使用

  • ES Module模块采用export和import关键字来实现模块化

    • export负责将模块内的内容导出
    • import负责从其他模块导入内容
  1. foo.js
const name = "why";
const age = "18";
function sayHello() {
    console.log("sayHello");
}
// 导出export
export {
    name,
    age,
    sayHello
}
  1. main.js
// 导入 import
// 注意事项一: 在浏览器中直接使用esmodule时,必须在文件后加上后缀名.js
import { name,age,sayHello } from "./foo.js";
console.log(name);
console.log(age);
sayHello();
  1. index.js
 <!-- 注意事项二:在我们打开对应的html时,如果html中有使用模块化的代码,那么必须开启一个服务器来打开 -->
    <script src="./foo.js" type="module"></script>
    <script src="./main.js" type="module"></script>

1.2. 导入和导出的三种方式

1.2.1 导出的三种方式:
  • 方式一:在语句声明的前面直接加上export关键字

    export const name = "why";
    export const age = "18";
    export function sayHello() {
        console.log("sayHello");
    }
    ​
    
  • 方式二:将所有需要导出的标识符,放到export后面的 {}中

    export {
        name,
        age,
        sayHello
    }
    
  • 方式三:导出时给标识符起一个别名

    // 2.导出方式二:导出时给标识符起一个别名
    export {
        name as fname,
        age,
        sayHello
    }
    
1.2.2 导入的三种方式
  • 方式一:import {标识符列表} from '模块'

    import { name,age,sayHello } from "./foo.js";
    
  • 方式二:导入时给标识符起别名

    // 2.导入方式二:导入时给标识符起别名
    import { name as fname,age,sayHello } from "./foo.js";
    
  • 方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上

    // 3.导入时可以给整个模块起别名
    import * as foo from "./foo.js"
    const name = "main";
    console.log(name);
    console.log(foo.name);
    console.log(foo.age);
    foo.sayHello();
    

1.3. 默认导出/导入

  • 默认导出export时可以不需要指定名字
  • 在导入时不需要使用 {},并且可以自己来指定名字
  • 它方便我们和现有的CommonJS等规范相互操作

1.1 parse_lyric

//  1.默认的导出
// 1.1 定义函数
function parseLyric() {
  return ["歌词"];
}
​
// export {
//     parseLyric
// }// 2.2 默认导出
// export default parseLyric// 2.定义标识符直接作为默认导出
export default function () {
  return ["新歌词"];
}
​
// 注意事项:一个模块只能有一个默认导出

1.2 main.js

// import { parseLyric } from "./parse_lyric.js";

import parseLyric from "./parse_lyric.js";
console.log(parseLyric());

1.4. export和import结合

utils

  1. format.js

    export function formatCount() {
      return "300万";
    }
    ​
    export function formatDate() {
      re
    
  2. parse.js

    export function parseLyric(lyricString) {
      return ["歌词"];
    }
    
  3. index.js

    import { formatCount,formatDate } from "./format.js";
    import { parseLyric } from "./parse.js";
    export {
        formatCount,
        formatDate,
        parseLyric
    }
    

    1.1 优化一:

    export { formatCount, formatDate } from "./format.js";
    export { parseLyric } from "./parse.js";
    

    1.2 优化二:

    export * from "./format.js";
    export * from "./parse.js";
    
  4. main.js

    import { formatCount, formatDate } from "./utils/format.js";
    import { parseLyric } from "./utils/parse.js";
    ​
    
  5. index.js

    <script src="./main.js" type="module"></script>
    

1.5. import函数

  • 不允许在逻辑代码中编写import导入声明语法,只能写到js代码顶层
// import函数的使用
let flag = true;
if (flag) {
  // 不允许在逻辑代码中编写import导入声明语法,只能写到js代码顶层
  //   import { name, age, sayHello } from "./foo.js";
  //   console.log(name, age);
​
  // 如果确实是逻辑成立时,才需要导入某个模块
  // import函数
  // const importPromise = import("./foo.js");
  //  importPromise.then(res => {
  //    console.log(res.name, res.age);
  //  });
​
  import("./foo.js").then(res => {
    console.log(res.name, res.age);
  });
  console.log("---------");
}

1.6. ESModule原理

  • ES Module的解析过程可以划分为三个阶段

    • 阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record)
    • 阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
    • 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中

二. 包管理工具

2.1. 代码共享

  • 方式一:上传到GitHub上、其他程序员通过GitHub下载我们的代码手动的引用

    • 缺点是大家必须知道你的代码GitHub的地址,并且从GitHub上手动下载
    • 需要在自己的项目中手动的引用,并且管理相关的依赖
    • 不需要使用的时候,需要手动来删除相关的依赖
    • 当遇到版本升级或者切换时,需要重复上面的操作
  • 方式二:使用一个专业的工具来管理我们的代码

    • 我们通过工具将代码发布到特定的位置
    • 其他程序员直接通过工具来安装、升级、删除我们的工具代码

2.2. npm配置文件

  • 对于一个项目来说,我们如何使用 npm来管理这么多包呢?
  • 事实上,我们每一个项目都会有一个对应的配置文件,无论是前端项目(Vue、React)还是后端项目
  • 这个配置文件会记录着你项目的名称、版本号、项目描述等
  • 也会记录着你项目所依赖的其他库的信息和依赖库的版本号
  • 这个配置文件就是package.json
那么这个配置文件如何得到呢?
  • 方式一:手动从零创建项目,npm init –y
  • 方式二:通过脚手架创建项目,脚手架会帮助我们生成package.json,并且里面有相关的配置
常见的属性
  • 必须填写的属性:name、version

    name是项目的名称

    version是当前项目的版本号

    description是描述信息,很多时候是作为项目的基本描述

    author是作者相关信息(发布时用到)

    license是开源协议(发布时用到)

    main属性: 设置程序的入口

    • 比如我们使用axios模块 const axios = require('axios')
    • 如果有main属性,实际上是找到对应的main属性查找文件的

    scripts属性

    • scripts属性用于配置一些脚本命令,以键值对的形式存在

    • 配置后我们可以通过 npm run 命令的key来执行这个命令

      • npm start和npm run start的区别是什么?

        • 它们是等价的
        • 对于常用的 start、 test、stop、restart可以省略掉run直接通过 npm start等方式运行
    • dependencies属性

      • dependencies属性是指定无论开发环境还是生成环境都需要依赖的包
      • 通常是我们项目实际开发用到的一些库模块vue、vuex、vue-router、react、react-dom、axios等等
      • 与之对应的是devDependencies
    • devDependencies属性

      • 一些包在生成环境是不需要的,比如webpack、babel等
      • 这个时候我们会通过 npm install webpack --save-dev,将它安装到devDependencies属性中
    • peerDependencies属性

      • 还有一种项目依赖关系是对等依赖,也就是你依赖的一个包,它必须是以另外一个宿主包为前提的
      • 比如element-plus是依赖于vue3的,ant design是依赖于react、react-dom
  • private属性

    • private属性记录当前的项目是否是私有的
    • 当值为true时,npm是不能发布它的,这是防止私有项目或模块发布出去的方式

2.3. npm install 命令

2.3.1 安装npm包分两种情况:

  • 全局安装(global install): npm install webpack -g
  • 局部安装(local install): npm install webpack

2.3.2 全局安装

  • 全局安装是直接将某个包安装到全局

  • 比如全局安装yarn

    • npm install yarn -g

2.3.3 但是很多人对全局安装有一些误会:

  • 通常使用npm全局安装的包都是一些工具包:yarn、webpack等
  • 并不是类似于 axios、express、koa等库文件
  • 所以全局安装了之后并不能让我们在所有的项目中使用 axios等库

2.4. npm install原理

npm install会检测是有package-lock.json文件:

  • 没有lock文件

    • 分析依赖关系,这是因为我们可能包会依赖其他的包,并且多个包之间会产生相同依赖的情况
    • 从registry仓库中下载压缩包(如果我们设置了镜像,那么会从镜像服务器下载压缩包)
    • 获取到压缩包后会对压缩包进行缓存(从npm5开始有的)
    • 将压缩包解压到项目的node_modules文件夹中(前面我们讲过,require的查找顺序会在该包下面查找)
  • 有lock文件

    • 检测lock中包的版本是否和package.json中一致(会按照semver版本规范检测)
    • 不一致,那么会重新构建依赖关系,直接会走顶层的流程
    • 一致的情况下,会去优先查找缓存
    • 没有找到,会从registry仓库下载,直接走顶层流程
    • 查找到,会获取缓存中的压缩文件,并且将压缩文件解压到node_modules文件夹中

2.5. 其他命令解析和查看

  • 卸载某个依赖包:
npm uninstall package
npm uninstall package --save-dev
npm uninstall package -D
  • 强制重新build
npm rebuild
  • 清除缓存
npm cache clean

2.7. yarn的历史和使用

  • yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具
  • yarn 是为了弥补 早期npm 的一些缺陷而出现的
  • 早期的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题
  • 虽然从npm5版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn

22.png.jpg

2.8. cnpm的使用(镜像服务器)

  • 由于一些特殊的原因,某些情况下我们没办法很好的从 registry.npmjs.org下载下来一些需要的包。

    • 查看npm镜像:

      npm config get registry # npm config get registry
      
    • 我们可以直接设置npm的镜像:

      npm config set registry https://registry.npm.taobao.org
      
  • 但是对于大多数人来说(比如我),并不希望将npm镜像修改了:

    • 第一,不太希望随意修改npm原本从官方下来包的渠道
    • 第二,担心某天淘宝的镜像挂了或者不维护了,又要改来改去
  • 这个时候,我们可以使用cnpm,并且将cnpm设置为淘宝的镜像:

npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm config get registry # https://r.npm.taobao.org/

2.9 npx工具

  • npx是npm5.2之后自带的一个命令。

  • 那么如何使用项目(局部)的webpack,常见的是两种方式

    • 方式一:明确查找到node_module下面的webpack

      • ./node_modules/.bin/webpack --version
        
    • 方式二:在 scripts定义脚本,来执行webpack

      • "scripts": {
        "webpack": "webpack --version"
        }
        
    • 方式三:使用npx

      • npx webpack --version
        

3.1 npm发布自己的包

  • 在命令行登录:

    • npm login
  • 修改package.json

  • 发布到npm registry上

    • npm publish
  • 更新仓库:

    • 1.修改版本号(最好符合semver规范)
    • 2.重新发布
  • 删除发布的包:

    • npm unpublish
  • 让发布的包过期:

    • npm deprecate

3.2 硬链接和软连接的概念

  • 硬链接(hard link):

    • 硬链接(英语:hard link)是电脑文件系统中的多个文件平等地共享同一个文件存储单元
    • 删除一个文件名字后,还可以用其它名字继续访问该文件

符号链接(软链接soft link、Symbolic link):

  • 符号链接(软链接、Symbolic link)是一类特殊的文件
  • 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用

文件的拷贝: 文件的拷贝每个人都非常熟悉,会在硬盘中复制出来一份新的文件数据

window: copy foo.js foo_copy.js
​
macos : cp foo.js foo_copy.js
  • 文件的硬链接
window: mklink /H aaa_hard.js aaa.js

macos : ln foo.js foo_hard.js
  • 文件的软连接:
window: mklink aaa_soft.js aaa.js

macos : ln -s foo.js foo_copy.js

3.3 pnpm的安装和使用

  • 那么我们应该如何安装pnpm呢?

    • 官网提供了很多种方式来安装pnpm:www.pnpm.cn/installatio…
    • 因为我们安装过Node,Node中有npm,所以我们通过npm安装即可

使用npm

npm install -g pnpm

33.png.jpg