浅谈一个webpack构建速度优化误区


Posted in Javascript onJune 24, 2019

问题描述

项目中使用了一个npm包a。前几天一直用得好好的,突然某次拉了别的分支代码后,就出Bug了。

第一反应是别人把这个包的版本变了。查看了下项目的package.jsonpackage-lock.json文件,该模块和依赖模块的信息并没有改变,node_modules/a中的版本信息也和package.json中的对应。

一下子没了头绪,只好到node_modules中去调试一下。

TL;DR;

拉到最后看总结 XD

node_modules目录结构

项目中node_modules目录如下:

node_modules
│
└───a
│  │  index.js
|  |  ...
│  │
│  └───node_modules
│    │  ...
│    └───c
|      |  index.js
|      |  ...
│  
└───c
  │  index.js
  │  ...

从该目录结构中可以发现,模块a的目录下还有一个node_modules目录,这个目录里放的是模块a的依赖。眼尖的同学可能发现了,项目本身的node_modules目录和a模块的node_modules目录中都有安装了模块c。这是为什么呢?

原因有2个:

  1. 项目直接依赖了模块c
  2. 项目没有直接依赖模块c,但是项目直接依赖的模块b中依赖了模块c,并且和a中依赖的模块c版本不兼容。

我们的项目中并没有直接引用模块c,所以是第二种情况。

npm的模块安装机制

本节主要解释为什么项目没有直接依赖模块c,却会把c安装在项目的node_modules目录下。不感兴趣的同学可以直接跳过。

假设项目依赖了模块a和模块b,模块a依赖模块c的1.0.0版本,模块b依赖模块c的2.0.0版本。

npm2

在npm2的时候,使用嵌套的方式来安装模块,c模块分别被安装到a和b模块的node_modules目录中。

浅谈一个webpack构建速度优化误区

这种方式虽然简单,但是却会导致node_modules中存在大量相同的模块。想象一下,如果模块a和模块依赖的模块c都是1.0.0版本,使用这种方式就会产生冗余的模块。

npm3

到npm3的时候,npm2中产生冗余模块的情况得到改善。npm安装模块时会尽量把模块安装到最外层的node_modules目录中,让模块能够尽量被复用。

  1. 安装模块时,如果外层node_modules目录中没有同名模块,就将其安装到最外层ndoe_modules目录中
  2. 如果外层node_modules目录中已经存在了同名模块,并且版本兼容,则不再安装(使用时直接使用外层模块)
  3. 如果外层node_modules目录中已经存在了同名模块,并且版本不兼容,则安装在父模块的node_modules目录中

上述情况的安装模块如图

浅谈一个webpack构建速度优化误区

引用了错误的模块

node_modules/a/node_modules/c/index.js中加了一些log,发现居然没执行!?

到这一步,要么是log的位置没写对,要么是没有引入这个模块。确认了webpack配置中的resolve.mainFields属性和模块c的package.json文件信息后,排除了第一种可能。

Tips: resolve.mainFields属性用来告诉webpack,引入一个npm模块时,如何找到这个模块的入口。

这时候已经有点懵了,引用模块时不是先从当前目录下的node_modules目录中开始一级一级向上查到吗?说到向上查找,那便到上一级的node_modules目录中去试一试。

果然!引用的是最外层node_modules中的模块c!

难道webpack查找模块的方式和Node.js不一样吗?还是因为webpack的某些配置导致的?

使用如下webpack配置来构建,发现并没有存在上述问题。

const path = require('path')
module.exports = {
 entry: './src/index.js',
 output: {
  filename: 'main.js',
  path: path.resolve(__dirname, './dist/js')
 },
};

那接下来只要排查到底是哪些webpack配置影响到模块检索。查看项目中的webpack配置,和模块检索相关的只有resolve属性。

const config = {
 resolve: {
  modules: [
    path.resolve(projectDir, 'src'),
    path.resolve(projectDir, 'node_modules'),
    path.resolve(imtPath, 'node_modules'),
  ],
  // es tree-shaking
  mainFields: ['jsnext:main', 'browser', 'main'],
  alias: {},
  extensions: ['.jsx', '.js'],
 }
}

所幸配置不多,对着webpack文档查一下,很快便找到了问题:resolve.modules中使用了绝对路径。以下为webpack文档原文:

告诉 webpack 解析模块时应该搜索的目录。

绝对路径和相对路径都能使用,但是要知道它们之间有一点差异。

通过查看当前目录以及祖先路径(即 ./node_modules, ../node_modules 等等),相对路径将类似于 Node 查找 'node_modules' 的方式进行查找。

使用绝对路径,将只在给定目录中搜索。

上述webpack配置中,path.resolve(projectDir, 'node_modules')为项目的node_modules目录。这样配置的原因,是因为想要优化模块检索的速度,结果却导致了这么严重的Bug。

根据webpack文档,就是因为这个绝对路径导致了Bug。那么只要把这个绝对路径换成node_modules,Bug便解决了。

总结

npm在安装模块时,会优先将包安装在node_modules目录的最外层,除非有冲突才会安装到父模块下的node_modules中。而webpack配置中的resolve.modules设置成项目node_modules目录的绝对路径时,会导致webpack在查找node_modules目录时,只在最外层目录查找,忽略掉更深层次的同名模块。这与默认的查找策略“优先使用深层模块”相反,导致构建时使用了错误的npm包。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery 图片延迟加载并等比缩放插件
Nov 09 Javascript
jquery 设置元素相对于另一个元素的top值(实例代码)
Nov 06 Javascript
jquery层级选择器的实现(匹配后代元素div)
Sep 05 Javascript
Vue.js 2.0 和 React、Augular等其他前端框架大比拼
Oct 08 Javascript
详解能在多种前端框架下使用的表格控件
Jan 11 Javascript
基于JavaScript实现验证码功能
Apr 01 Javascript
Bootstrap Table使用整理(二)
Jun 09 Javascript
p5.js入门教程之键盘交互
Mar 19 Javascript
微信小程序网络请求实现过程解析
Nov 06 Javascript
vue-cli3.0实现一个多页面应用的历奇经历记录总结
Mar 16 Javascript
vue点击标签切换选中及互相排斥操作
Jul 17 Javascript
分享一个vue实现的记事本功能案例
Apr 11 Vue.js
vue项目中运用webpack动态配置打包多种环境域名的方法
Jun 24 #Javascript
Vue.js+cube-ui(Scroll组件)实现类似头条效果的横向滚动导航条
Jun 24 #Javascript
JavaScript学习教程之cookie与webstorage
Jun 23 #Javascript
React组件对子组件children进行加强的方法
Jun 23 #Javascript
vue使用websocket的方法实例分析
Jun 22 #Javascript
JS实现简单的文字无缝上下滚动功能示例
Jun 22 #Javascript
js图片查看器插件用法示例
Jun 22 #Javascript
You might like
MySQL GBK→UTF-8编码转换
2007/05/24 PHP
php实现QQ空间获取当前用户的用户名并生成图片
2015/07/25 PHP
PHP接入支付宝接口失效流程详解
2020/11/10 PHP
JavaScript 学习点滴记录
2009/04/24 Javascript
js数组方法扩展实现数组统计函数
2014/04/09 Javascript
jquery中validate与form插件提交的方式小结
2016/03/26 Javascript
使用jquery提交form表单并自定义action的方法
2016/05/25 Javascript
js与jquery正则验证电子邮箱、手机号、邮政编码的方法
2016/07/04 Javascript
html+js实现简单的计算器代码(加减乘除)
2016/07/12 Javascript
vue过渡和animate.css结合使用详解
2017/06/14 Javascript
Angular模版驱动表单的使用总结
2018/05/05 Javascript
vue实现商品加减计算总价的实例代码
2018/08/12 Javascript
layer.open的自适应及居中及子页面标题的修改方法
2019/09/05 Javascript
Vue使用NProgress的操作过程解析
2019/10/10 Javascript
js 使用ajax设置和获取自定义header信息的方法小结
2020/03/12 Javascript
[52:12]FNATIC vs Infamous 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
使用WingPro 7 设置Python路径的方法
2019/07/24 Python
浅谈Python3识别判断图片主要颜色并和颜色库进行对比的方法
2019/10/25 Python
利用Pytorch实现简单的线性回归算法
2020/01/15 Python
Python-opencv 双线性插值实例
2020/01/17 Python
基于Python实现人脸自动戴口罩系统
2020/02/06 Python
Django数据库操作之save与update的使用
2020/04/01 Python
Python如何使用input函数获取输入
2020/08/06 Python
matplotlib基础绘图命令之bar的使用方法
2020/08/13 Python
HTML5 实现图片上传预处理功能
2020/02/06 HTML / CSS
如何使用canvas绘制可移动网格的示例代码
2020/12/14 HTML / CSS
双立人美国官方商店:ZWILLING集团餐具和炊具
2020/05/07 全球购物
《小山羊和小灰兔》教学反思
2014/02/19 职场文书
少先队学雷锋活动月总结
2014/03/09 职场文书
2015医院个人工作总结范文
2015/05/21 职场文书
惊涛骇浪观后感
2015/06/05 职场文书
开工典礼致辞
2015/07/29 职场文书
公司规章制度范本
2015/08/03 职场文书
初中语文教学反思范文
2016/03/03 职场文书
90后经典动画片排行:《数码宝贝》第二,《小鲤鱼历险记》在榜
2022/03/18 日漫
JavaScript设计模式之原型模式详情
2022/06/21 Javascript