npm link 用法简介

注意:本文内容仅适用于 Linux 或者 MacOS。 软链接和硬链接 了解 npm link 用法之前,我们先来看看 Linux 链接。 在类 Unix 系统中,如果一个文件需要在多个地方被用到,不需要把文件复制到多个地方,我们可以把这个文件放在固定的位置,需要用到该文件的地方可以使用链接的方式来引用这个文件。 链接又分为两种,硬链接(hard link)和符号链接(symbolic link),符号链接又叫软链接。 硬链接的效果类似于 js 对象的引用: var obj1 = { a: 1 } var obj2 = obj1 obj1 = null console.log(obj2.a) // 1 当对文件 a 创建一个硬链接 b,那么 a 和 b 实际上指向了同一个文件,a 和 b 都是这个文件的硬链接,a 和 b 除了文件名称不同并无其他区别,修改 a 的内容 b 的内容也会变化,因为 a 和 b 只是同一个文件的引用。删除文件只有当 a 和 b 都被删除,文件空间才会被释放。 Linux 中使用 ln 命令来创建硬链接: 创建 a.txt 文件,并写入内容:abc $ echo 'abc' >> a.txt 给 a.txt 创建硬链接 b.txt $ ln a.txt b.txt 修改 b.txt $ echo 'bbb' >> b.txt 打印 a.txt $ echo a.txt 打印结果: abc bbb 软链接类似于 Windows 中的快捷方式,它只包含了链接文件的位置信息,使用 ln -s 来创建软链接: 创建 a.txt 文件,并写入内容:abc $ echo 'abc' >> a.txt 给 a.txt 创建硬链接 b.txt $ ln -s a.txt b.txt 打印 b.txt 的内容 $ echo b.txt $ abc ls -l 查看文件列表 $ ls -l .rw-r--r-- 8 mac 14 9 16:50 a.txt lrwxr-xr-x 5 mac 14 9 16:55 b.txt -> a.txt 使用 ls -l 查看文件列表可以看到软链接文件有一个箭头指向了链接的文件。 npm link npm link 有什么作用呢? 当我们在开发一个 npm 包的时候,如果开发完成了想要在发布到 npm 之前在项目试用一下,这个时候就可以用 npm link 来把这个 npm 包链接到项目中,npm link 创建的是软链接。 假如我们实现一个 npm 包,名为 my-lib,代码结果如下所示: // ~/my-lib // ~/my-lib/package.json { "name": "my-lib", "version": "1.0.0", "main": "src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } } // ~/my-lib/src/index.js console.log('Hello World') 然后我们有一个项目想要在本地试用这个包,假如这项目名为 my-project,代码入下所示: // ~/my-project // ~/my-project/package.json { "name": "my-project", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } } // ~/my-project/src/index.js require('my-lib') 然后我们在 my-project 目录下执行命令来把 my-lib 包链接到项目中: $ npm link ~/my-lib 执行成功之后可以看到 node_modules 下面会出现 my-lib 包,此时我们在 my-project 下执行命令来运行一下 src/index.js: $ node src/index.js $ Hello World 可以看到会打印出 Hello World,说明成功执行了 my-lib 的代码。 我们还可以在 my-lib 下执行命令把包链接到 npm 全局包安装目录: $ cd ~/my-lib $ npm link 查看全局 npm 包 /Users/macbook/.nvm/versions/node/v16.17.0/lib ├── corepack@0.13.0 ├── my-lib@1.0.0 -> ./../../../../../my-lib └── npm@8.18.0 可以看到 my-lib 已经链接到了 npm 全局包中,此时可以在任意项目中执行以下命令把 my-lib 包链接到项目中: 无需再写 my-lib 的完成路径 $ npm link my-lib 通过 npm link 命令可以很方便的在项目使用未发布的包,以便在实际项目中来测试未发布的包。
2022年09月15日
编程技术

Vue 2.7 源码分析简介

本文持续更新中... 命令式和声明式 了解源码之前先来看看命令式编程范式和声明式编程范式。 命令式编程,又叫指令式编程,是一种描述行为过程的编程范式。C 语言,C++ 语言,Java 语言,JavaScript 语言等都是命令式编程语言。 const div = document.querySelector("#app"); // 获取 div div.innerText = "hello world"; // 设置文本内容 div.addEventListener("click", () => { alert("ok"); }); // 绑定点击事件 可以看到我们想要实现什么是通过一步步告诉计算机应该怎样做来实现的,这也符合人的行为方式,但是会感觉有点累。 声明式编程,和命令式相对立,是一种告诉计算机想要的结果的编程范式。比如数据库查询语言,正则表达式,函数式编程等就是声明式编程。 select id, name from users; 从这段 SQL 查询语句可以看出,我们只是告诉了计算机从 users 表中去找出所有用户的 id 和 name,而不用关心具体怎样去查找。 了解了这两种编程范式,我们再回想一下 Vue 的用法,和全面 js 代码一样的功能用 Vue 可以用下面的代码实现: alert('ok')">hello world 我们也是只表达了想要一个 div 里面包含 hello world 字符串,然后点击 div 会弹出 ok 字符串这样的一个结果,由此可以看出 Vue 在使用方式上是一个声明式框架。所以我们在使用 Vue 实现自己的功能需求时,不会像使用命令式编程(比如 jQuery)那样项目大了就会有沉重的心智负担。 源码结构 Vue 核心源码目录如下图所示: Vue源码 compiler compiler 下的代码实现了 Vue 的模板编译器,用于把 Vue 模板编译为 render 函数,用于渲染虚拟 DOM。 // src/compiler/index.ts import { parse } from "./parser/index"; import { optimize } from "./optimizer"; import { generate } from "./codegen/index"; import { createCompilerCreator } from "./create-compiler"; import { CompilerOptions, CompiledResult } from "types/compiler"; // createCompilerCreator allows creating compilers that use alternative // parser/optimizer/codegen, e.g the SSR optimizing compiler. // Here we just export a default compiler using the default parts. export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options); if (options.optimize !== false) { optimize(ast, options); } const code = generate(ast, options); return { ast, render: code.render, staticRenderFns: code.staticRenderFns, }; }); 从入口文件可以看到,其主要工作分三步: parse(template.trim(), options):把模板解析成 AST 语法树 optimize(ast, options):优化 AST 树,检测出静态子 AST 树,把静态树设置为常量,在重新渲染时的 patch 阶段直接跳过这些树的更新,优化了重新渲染的效率 generate(ast, options):根据 AST 生成 render 函数 core core 目录下是 Vue 核心功能代码。 components components 下是 Vue 内置组件的实现,这里只有一个组件,就是 keep-alive 组件。transition 和 transition-group 是专为 web 平台实现的组件,源码在 platforms 下。 global-api global-api 下实现了 Vue 的一些全局 API,比如 Vue.use、Vue.mixin、Vue.extend、Vue.component、Vue.directive、Vue.filter 等。 instance instance 下实现了 Vue 构造函数,使得我们可以通过 new Vue() 来创建 Vue 实例。这里也是 Vue 真正的核心,instance/index.ts 入口文件的代码非常简洁,如下所示: import { initMixin } from "./init"; import { stateMixin } from "./state"; import { renderMixin } from "./render"; import { eventsMixin } from "./events"; import { lifecycleMixin } from "./lifecycle"; import { warn } from "../util/index"; import type { GlobalAPI } from "types/global-api"; function Vue(options) { if (DEV && !(this instanceof Vue)) { warn("Vue is a constructor and should be called with the new keyword"); } this._init(options); } initMixin(Vue); stateMixin(Vue); eventsMixin(Vue); lifecycleMixin(Vue); renderMixin(Vue); export default Vue as unknown as GlobalAPI; 其中,initMixin(Vue) 是把 _init 方法挂载到 Vue 原型上面,从上面的代码可以看到执行 new Vue({}) 的时候会调用 _init 方法来构建 Vue 实例,_init 方法主要包含以下内容: 合并构造函数选项 初始化生命周期 初始化组件监听事件 初始化渲染函数 触发 beforeCreate 钩子函数 初始化 injections 初始化组件的 props、setup(Composition API)、methods、data、computed、watch 初始化 provide 触发 created 钩子函数 如果传了 $options.el,则挂载组件 stateMixin(Vue) 是在 Vue 原型上面挂载 $data 和 $props 属性,以及 $set、$delete 和 $watch 用于处理 data 的方法。 eventsMixin(Vue) 是在 Vue 原型上面挂载 $on、$once、$off 和 $emit 等处理事件的方法。 lifecycleMixin(Vue) 是在 Vue 原型上面挂载 $forceUpdate 和 $destroy 可以手动调用的生命周期方法。 renderMixin(Vue) 是在 Vue 原型上面挂载 $nextTick 方法和内部 _render 方法。 observer observer 下的代码实现了 Vue 的数据响应式功能。主要包括实现数据劫持的 Observer,实现依赖收集的 Dep 和实现观察者的 Watcher。 util util 下实现了一下工具函数,比如开发环境下的日志打印,运行环境监测函数,错误处理数据,处理 options 的一些函数,处理 props 的一些函数,以及 nextTick 的实现等。 vdom vdom 下主要实现了虚拟 DOM。 platforms platforms 针对不同平台实现的编译器,因为 Weex 项目已经废弃,现在只剩下 Web 平台了。 shared shared 定义了一些公共方法和常量。 types types 存放的是 typescript 类型声明。 v3 v3 中实现了 Vue 3 中的语法,比如组合式 API 等,在 Vue 2 中也可以写组合式 API 了。
2022年09月05日
编程技术