10 工程化
bundleless少打包或者不打包 esm有了浏览器的支持才有bundleless方案
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。 由于现代浏览器本身就支持ESModule,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。 由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。 当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译 时间,项目越复杂、模块越多,vite的优势越明显。 在HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。 当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ESModule,因此在代码中不可以使用CommonJS
Webpack
webpack scope hoisting
webpack作用域优化
scopehoisting是webpack的内置优化,它是针对模块的优化。在生产环境打包时会自动开启。 在未开启scopehoisting时,webpack会将每个模块的代码放置在一个独立的函数环境中,这样是为了保证模块的作用域互不干扰。 scope hoisting的作用恰恰相反是把多个模块的代码合并到一个函数环境中执行。在这一过程中,webpack会按照顺序正确的合并模块代码,同时对涉及的标识符做适当处理以避免重名。 这样做的好处是减少了函数调用,对运行效率有一定提升,同时也降低了打包体积。 但scope hoisting的启用是有前提的,如果遇到某些模块多次被其他模块引用,或者使用了动态导入的模块,或者是非ESM的模块,都不会有scopehoisting。
vite和webpack的区别
| vite | webpack | |
|---|---|---|
| 打包 | 不打包(预编译) | 每次打包都是全量打包 |
| 启动速度 | 快 | 慢(因为要打包) |
| hmr | 快(因为是局部刷新) | 慢(热更新要重新打包) |
| 配置复杂度 | 简单 | 复杂 |
| 理念 | 少打包甚至不打包 | 先打包再运行 |
| 路由跳转 | 有白屏(因为要加载资源、编译等) | 无白屏(因为所有资源都已经打包好,在进入时就加载好了) |
| 生态 | 不如webpack成熟,而且是新兴的构建工具缺少对旧版本浏览器的支持(需要使用社区插件) | 成熟,兼容性好 |
| 生产环境 | 较为复杂,因为编译和打包使用是不同的工具,所以可能会造成不一致 | 统一 |
模块联邦
用于解决微前端,不同模块之间代码复用问题 在webpack5提出的,是webpack的一个插件:ModuleFederationPlugin
npm安装机制
- npm会检查本地的node_modules目录中是否已经安装过该模块,如果已经安装,则不再重新安装
- npm检查缓存中是否有相同的模块,如果有,直接从缓存中读取安装
- 如果本地和缓存中均不存在,npm会从registry指定的地址下载安装包,然后将其写入到本地的node_modules目录中,同时缓存起来。
CommonJS模块化实现原理
根据文件路径,找到模块文件,读取文件内容进行执行
- 为了保证高效的执行,仅加载必要的模块,node只有再执行到require函数是才会加载并执行模块
- 为了隐藏模块中的代码,node执行模块的时候会放到一个立即执行函数中执行,保证不污染全局变量
- 为了保证顺利的导出模块内容,nodejs做了以下处理: 在模块开始执行前,初始化一个值module.exports={},module.exports即模块的导出值。为了方便开发者便捷的导出,Codejs在初始化完module.exports后,又声明了一个变量exports=module.exports
(function(module){
module.exports={}
var exports=module.exports
//模块中的代码
return module.exports
})()- 为了避免反复加载同一个模块,nodejs默认开启了模块缓存,如果加载 的模块已经被加载过了,则会自动使用之前的导出结果
缺点: CommonJS是同步的,必须要等到加载完文件并执行完代码后才能继续向后执行 由于node运行在本机上,因此读取是在磁盘上读取,速度较快,但换到浏览器环境就不一样了,在浏览器中运行需要从网络上获取文件
import引入机制
使用.、/等符合开头的会从当前目录找 直接模块名开头的会从node_modules文件夹中找
包管理工具
npm
最开始的 npm(npm2 版本)采用的是树形结构,也就是在 node_modules 中,只有依赖 A 的文件夹,A 文件夹下有 B,C 文件夹,B,C 文件夹下分别有 D 文件夹。
很明显,可以看出一个问题,就是 B,C 文件夹下都有依赖 D,也就是依赖重复,会占据比较大的磁盘空间。 另一个问题是,这种嵌套的方式,会导致有一些操作系统(比如 windows 的文件路径最长是 260 多个字符),如果嵌套过多就会出问题。
于是,社区就出来新的解决方案了,就是 yarn。
yarn
yarn 的解决方案就是直接平铺开来,node_modules 下面直接就是依赖 A,B,C,D 四个文件夹,不存在嵌套关系。
npm 后来升级到 3 之后,也是采用这种铺平的方案了,和 yarn 很类似。
这样,貌似重复依赖和嵌套过深的问题就解决了,但是却引入了新的问题:“幽灵依赖”。
还是以上面的例子为例,本来我只 install 了依赖 A,但是因为平铺开来,导致 node_modules 就有 A,B,C,D 了,那么我就直接可以在代码中 import B,C,D 了。 也就是说,我没有 install 却可以直接用。
这样会带来两个问题:
- 在代码直接导入 B,将来 A 版本升级了,A 的依赖 B 页升级了,导致代码报错,这样的问题很难发现,为什么我升级 A,别的地方却出错了呢?问题很难定位。
- 假如 A 是开发依赖,也就是不会打包到最终的 dist 里面,那么最终的包里面是没有依赖 B 的,那引用 B 的地方在生产环境就会报错了。 所以又推出了pnpm来解决“幽灵依赖”。
pnpm
pnpm 也是一个包管理工具,它通过硬连接和软链接的方式来减少重复依赖的复制,节省了磁盘空间。
它解决了 npm2 的依赖复制和路径过长的问题,也避免了 yarn 和 npm3+ 的幽灵依赖和多次复制依赖的问题。
当我们使用pnpm install的时候,并没有把依赖安装到 node_modules 中,而是存在本地的一个 pnpm store 全局仓库中,然后在 node_modules 中通过软链接的方式链接到 store 仓库的,在 node_modules 中的依赖可以理解成一个快捷方式,基本不占用空间的。
在 node_modules 中,依赖采用的是树形结构,这样就避免了“幽灵依赖”,然后在 store 中采用扁平的结构,就避免了“重复依赖”和“嵌套过深”的问题,这两者一结合,就完美解决了 npm 和 yarn 的问题。
而且,这种方式还能带来一个好处就是“节省磁盘空间”。 如果我们本地有 10 个前端项目,那么安装后就会产生 10 份依赖存到本地,但是如果采用 pnpm store 的方式,如果有的项目采用相同的依赖,那么就不需要下载了,直接 link 到 store 即可。 另外,多个项目依赖一个库的不同版本,pnpm 会下载该库的不同版本放在 store 的,各个项目会引用不同版本的库,互不影响。
贡献者
版权所有
版权归属:wynnsimon
