挑战21天手写前端框架 day19 依赖预打包是什么意思

简介: 挑战21天手写前端框架 day19 依赖预打包是什么意思

image.png

先讲结论:依赖预打包就是将框架里面用到的第三方开源的库的代码,拷贝一份到你的框架中保存起来。然后定期升级维护,好处就是项目中足够稳定,比如现在 npm 上经常出现的底层包挂码事件等超高的安全风险问题,就可以通过这个方法完美的解决。更多关于这部分的内容,可以查看 umi 作者云谦的星球日更28 依赖版本锁不锁


原理

感觉原理没什么好讲的,就是将构建入口找到 node_modules 下的某个包指定的 main 入口,然后将它编译到我们指定的路径中。修改项目中的引用,从引用包的方式改成引用相对文件的方式。


用法

我们在需要预打包的子包目录下,执行 pnpm build:deps pkgName 来将子包依赖预编译到子包的 compiled

如:

cd packages/malita
pnpm build:deps express
复制代码

malita 子包中的 express 进行预编译操作。


实现

这个实现也算是之前知识点的一个复习环节。首先我们要执行 build:deps 命令我们需要先定义它,在 malita 子包的 packages.json 中的 scripts 配置中定义。我们可以这么写。

{
    "name": "malita",
    "scripts": {
        "build:deps": "pnpm esno ../../scripts/bundleDeps.ts",
    },
}
复制代码


使用 esno 是希望直接执行 ts 类型的脚本,而不用将脚本转化成 js 语法。因为这个操作会在很多个子包中执行,因此我们将这个脚本写在最外层,供任意子包使用。

// 最顶层目录下
pnpm i esno -w --D
复制代码


获取到传入参数

还记得我们第四天的内容,如何编写一个 cli 吗?我们那时候是使用 commander 来实现的,用 program.parse(process.argv) 取到命令行的参数。

今天我们换一种用法,使用另一个编写命令行时常用的库:minimist

pnpm i minimist @types/minimist -w --D
复制代码


新建 scripts/bundleDeps.ts 文件,写入

+ import minimist from 'minimist';
+ const argv = minimist(process.argv.slice(2));
+ console.log(argv);
复制代码


运行测试

cd packages/malita
pnpm build:deps express
// 日志
{ _: [ 'express' ] }
复制代码


取到入口和目标路径

如果你忘了接下去该做什么,注意回看我们的需求:“将构建入口找到 node_modules 下的某个包指定的 main 入口,然后将它编译到我们指定的路径中”。

import minimist from 'minimist';
import fs from 'fs-extra';
import path from 'path';
const argv = minimist(process.argv.slice(2));
// 找到 node_modules 下的
const nodeModulesPath = path.join(process.cwd(), 'node_modules');
// 某个包
const pkg = argv._[0];
// 的 main 入口
const entry = require.resolve(pkg, {
    paths: [nodeModulesPath],
});
// 将它编译
build()
// 到
writeFile()
// 我们指定的路径中
const target = `compiled/${pkg}`;
复制代码


然后问题的重点就回到了 buildwriteFile 方法的实现上。

采用这样的分解步骤,可以让我们的整个代码逻辑变得非常的清晰。然后先把我们的实现完成了,再去考虑代码的整洁性,改改 callback 到 promise ,逻辑调整啊之类的优化工作。我的理念就是不想太多,先做,先拿基础分。


使用 @vercel/ncc 预编译代码

// 没带 cd 的就是表示在最顶成根目录
pnpm i @vercel/ncc -w --D
复制代码
import minimist from 'minimist';
import fs from 'fs-extra';
import path from 'path';
// @ts-ignore
import ncc from '@vercel/ncc';
const argv = minimist(process.argv.slice(2));
const nodeModulesPath = path.join(process.cwd(), 'node_modules');
const pkg = argv._[0];
const entry = require.resolve(pkg, {
    paths: [nodeModulesPath],
});
const target = `compiled/${pkg}`;
ncc(entry, {
    minify: true,
    target: 'es5',
    assetBuilds: false,
}).then(({ code }: any) => {
    // entry code
    fs.ensureDirSync(target);
    fs.writeFileSync(path.join(target, 'index.js'), code, 'utf-8');
})
复制代码


运行验证

cd packages/malita
pnpm build:deps express
// 日志
> malita@0.0.2 build:deps /Users/congxiaochen/Documents/malita/packages/malita
> pnpm esno ../../scripts/bundleDeps.ts "express"
ncc: Version 0.33.4
ncc: Compiling file index.js into CJS
复制代码


查看目标文件是否正确生成 packages/malita/compiled/express/index.js

修改项目中的引用

- import express from 'express';
+ import express from '../compiled/express';
复制代码
cd packages/malita
pnpm build
复制代码
cd examples/app
pnpm dev
// 日志
> @examples/app@1.0.0 dev /Users/congxiaochen/Documents/malita/examples/app
> malita dev
App listening at http://127.0.0.1:8888
[HPM] Proxy created: /api  -> http://jsonplaceholder.typicode.com/
[HPM] Proxy rewrite rule created: "^/api" ~> ""
复制代码

页面功能正常访问。


添加类型定义

从上面的操作,我们可以看出来 express 依赖已经预编译成功了。但是我们会收到一个类型错误。

无法找到模块“../compiled/express”的声明文件。“/Users/congxiaochen/Documents/malita/packages/malita/compiled/express/index.js”隐式拥有 "any" 类型。

pnpm i dts-packer -w --D
复制代码
+ import { Package } from 'dts-packer';
+ new Package({
+     cwd: cwd,
+     name: pkg,
+     typesRoot: target,
+ });
复制代码


遗留问题

cd packages/malita
pnpm build:deps express
// 日志
TypeError: Cannot read properties of undefined (reading 'uid')
    at isDirectory (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/resolve@1.22.0/node_modules/resolve/lib/sync.js:31:23)
    at loadNodeModulesSync (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/resolve@1.22.0/node_modules/resolve/lib/sync.js:200:17)
    at Function.resolveSync [as sync] (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/resolve@1.22.0/node_modules/resolve/lib/sync.js:107:17)
    at Package.getEntryFile (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/dts-packer@0.0.3/node_modules/dts-packer/dist/Package.js:88:56)
    at Package.init (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/dts-packer@0.0.3/node_modules/dts-packer/dist/Package.js:37:32)
    at new Package (/Users/congxiaochen/Documents/malita/node_modules/.pnpm/dts-packer@0.0.3/node_modules/dts-packer/dist/Package.js:31:14)
    at null.build (/Users/congxiaochen/Documents/malita/scripts/bundleDeps.ts:52:5)
复制代码


有一个很难懂的日志,我们可以根据报错堆栈,定位到是加载 @types/express 的时候找不到包。但是注释掉 ncc 单独执行 Package 又能顺利生成。从11点调试到12点半,太晚了,就没有继续定位这个问题,如果有知道原因的小伙伴,记得指导我一下。

pnpm build:deps express
> malita@0.0.2 build:deps /Users/congxiaochen/Documents/malita/packages/malita
> pnpm esno ../../scripts/bundleDeps.ts "express"
@types/express
>  index.d.ts /Users/congxiaochen/Documents/malita/node_modules/@types/express/index.d.ts
// 略
>> dep import express
复制代码


类型文件找不到

完成了 express 的构建之后,我们继续构建其他的依赖,比如 commander

pnpm build:deps commander
> malita@0.0.2 build:deps /Users/congxiaochen/Documents/malita/packages/malita
> pnpm esno ../../scripts/bundleDeps.ts "commander"
ncc: Version 0.33.4
ncc: Compiling file index.js into CJS
>  typings/index.d.ts /Users/congxiaochen/Documents/malita/packages/malita/node_modules/commander/typings/index.d.ts
复制代码


我们会发现,commander 自己有 types 定义并且是在 typings/index.d.ts

因此如果我们在项目中使用 import xx from '../compiled/commander'; 就会提示无法找到 commander 模块。


因此我们还需要在构建的包下面生成一个虚拟的包,这个实现很简单,将原有的package.json 拷贝到目标目录下就行。

const pkgRoot = path.dirname(
        resolve.sync(`${pkg}/package.json`, {
            basedir: cwd,
        }),
    );
    if (fs.existsSync(path.join(pkgRoot, 'LICENSE'))) {
        fs.copyFileSync(path.join(pkgRoot, 'LICENSE'), path.join(target, 'LICENSE'))
    }
    fs.copyFileSync(path.join(pkgRoot, 'package.json'), path.join(target, 'package.json'))
复制代码


接下来就是将我们的其他依赖都进行预打包就可以了。这里只是讲解了如何实现和一点点注意事项,如果你想对这个内容了解的更清楚一点,包括上面用到的 dts-packer 包的详细实现和设计,请加入作者云谦的星球


感谢阅读,如果你觉得本文对你有一点点帮助,别忘了给我点赞加关注哦,感激不尽。

如果文中有错漏的地方,欢迎指出,太晚了有点困了。一稿出了,抱歉抱歉。


源码归档

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
资源调度 前端开发 算法
前端依赖版本重写指南
感谢神奇的 Semver 动态规则,npm 社区经常会发生依赖包更新后引入破坏变更的情况(应用没有使用依赖锁的话),而应用开发者就要在自己的依赖声明里先临时绕过,避免安装到有问题的版本,如果是一级依赖,只需要改 package.json 的声明就可以了,但如果是子依赖,就需要进行版本重写(overrides/resolution)了。本文是一篇针对版本重写功能的指南性文章,当你遇到如下的问题时,就可以按照对应的依赖重写语法,解决这些依赖问题了。
7849 1
前端依赖版本重写指南
|
5月前
|
前端开发 JavaScript Java
踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录
在软件开发的道路上,我们总是会遇到各种问题和挑战,这些问题可能是技术的限制,也可能是配置的误差。解决这些问题的过程通常是开发者成长的一部分。今天,我将与大家分享在开发过程中,涉及到 WordPress、MyBatis-Plus 和 前端依赖问题 时,我遇到的一些“坑”以及如何一步步解决它们的经验。
|
7月前
|
存储 缓存 资源调度
前端瘦身革命:告别臃肿的依赖管理
前端瘦身革命:告别臃肿的依赖管理
272 79
|
资源调度 监控 前端开发
第七章(原理篇) 微前端技术之依赖管理与版本控制
第七章(原理篇) 微前端技术之依赖管理与版本控制
649 0
|
前端开发 JavaScript 程序员
成功解决:尚硅谷中的谷粒商城前端项目运行依赖问题。【详细图解+问题说明+解决思路】
这篇文章介绍了如何解决尚硅谷谷粒商城前端项目中遇到的依赖问题,通过修改`package.json`和`package-lock.json`中的`node-sass`和`sass-loader`版本,成功解决了node版本与这些依赖的兼容性问题。
成功解决:尚硅谷中的谷粒商城前端项目运行依赖问题。【详细图解+问题说明+解决思路】
|
前端开发 Oracle Java
【前端学java】java开发的依赖安装与环境配置(1)
【8月更文挑战第8天】java开发的依赖安装与环境配置
246 1
【前端学java】java开发的依赖安装与环境配置(1)
|
前端开发
前端学习笔记202305学习笔记第二十三天-vue3项目依赖安装
前端学习笔记202305学习笔记第二十三天-vue3项目依赖安装
108 0
前端学习笔记202305学习笔记第二十三天-vue3项目依赖安装
|
缓存 前端开发 JavaScript
前端架构思考:代码复用带来的隐形耦合,可能让大模型造轮子是更好的选择-从 CDN 依赖包被删导致个站打不开到数年前因11 行代码导致上千项目崩溃谈谈npm黑洞 - 统计下你的项目有多少个依赖吧!
最近,我的个人网站因免费CDN上的Vue.js包路径变更导致无法访问,引发了我对前端依赖管理的深刻反思。文章探讨了NPM依赖陷阱、开源库所有权与维护压力、NPM生态问题,并提出减少不必要的依赖、重视模块设计等建议,以提升前端项目的稳定性和可控性。通过“left_pad”事件及个人经历,强调了依赖管理的重要性和让大模型代替人造轮子的潜在收益
345 0
|
缓存 JSON 资源调度
前端包管理器的依赖管理原理
本文主要探究前端包管理器的依赖管理原理,希望对读者有所帮助。
876 0
前端包管理器的依赖管理原理
|
IDE 前端开发 搜索推荐
5款超好用的在线IDE,媲美vscode,可以直接编写前端构建化项目,而无需在本地下载依赖包,非常适合学习、demo、原型开发
5款超好用的在线IDE,媲美vscode,可以直接编写前端构建化项目,而无需在本地下载依赖包,非常适合学习、demo、原型开发
6788 0

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    689
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    289
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    254
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    203
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    309
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    451
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    198
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    144
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    209
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    284