浅谈一个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 相关文章推荐
表单元素事件 (Form Element Events)
Jul 17 Javascript
鼠标滚轮控制网页横向移动实现思路
Mar 22 Javascript
用jQuery实现的智能隐藏、滑动效果的返回顶部代码
Mar 18 Javascript
js识别不同浏览器基于userAgent做判断
Jul 29 Javascript
简述AngularJS的控制器的使用
Jun 16 Javascript
jquery实现最简单的滑动菜单效果代码
Sep 12 Javascript
jQuery实现的多张图无缝滚动效果【测试可用】
Sep 12 Javascript
深入理解Vue生命周期、手动挂载及挂载子组件
Sep 27 Javascript
JS 数组随机洗牌的实例代码
Sep 12 Javascript
nuxt.js中间件实现拦截权限判断的方法
Nov 21 Javascript
vue2.0基于vue-cli+element-ui制作树形treeTable
Apr 30 Javascript
js cavans实现静态滚动弹幕
May 21 Javascript
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
DIY实用性框形天线
2021/03/02 无线电
Apache, PHP在Windows 9x/NT下的安装与配置 (二)
2006/10/09 PHP
PHP 时间日期操作实战
2011/08/26 PHP
解析如何通过PHP函数获取当前运行的环境 来进行判断执行逻辑(小技巧)
2013/06/25 PHP
PHP读取汉字的点阵数据
2015/06/22 PHP
多浏览器兼容的获取元素和鼠标的位置的js代码
2009/12/15 Javascript
CSS+Jquery实现页面圆角框方法大全
2009/12/24 Javascript
jquery ajax实现下拉框三级无刷新联动,且保存保持选中值状态
2013/10/29 Javascript
JS实现控制表格只显示行边框或者只显示列边框的方法
2015/03/31 Javascript
jQuery获得字体颜色16位码的方法
2016/02/20 Javascript
页面向下滚动ajax获取数据的实现方法(兼容手机)
2016/05/24 Javascript
使用React实现轮播效果组件示例代码
2016/09/05 Javascript
Bootstrap文件上传组件之bootstrap fileinput
2016/11/25 Javascript
微信小程序小组件 基于Canvas实现直播点赞气泡效果
2020/05/29 Javascript
Vue 实现把表单form数据 转化成json格式的数据
2019/10/29 Javascript
Python运行的17个时新手常见错误小结
2012/08/07 Python
在Python中利用Pandas库处理大数据的简单介绍
2015/04/07 Python
python机器学习包mlxtend的安装和配置详解
2019/08/21 Python
Python 实现一行输入多个数字(用空格隔开)
2020/04/29 Python
python实现发送带附件的邮件代码分享
2020/09/22 Python
Python应用自动化部署工具Fabric原理及使用解析
2020/11/30 Python
python实现经纬度采样的示例代码
2020/12/10 Python
移动端Web页面的CSS3 flex布局快速上手指南
2016/05/31 HTML / CSS
世界最大的私人旅行指南出版商:孤独星球
2016/08/23 全球购物
澳大利亚最便宜的网上药房:Chemist Warehouse
2020/01/30 全球购物
半年思想汇报
2013/12/30 职场文书
法律六进活动方案
2014/03/13 职场文书
学习雷锋做美德少年寄语大全
2014/04/09 职场文书
煤矿安全生产责任书
2014/04/15 职场文书
统计工作个人总结
2015/03/03 职场文书
格林童话读书笔记
2015/06/30 职场文书
运动员加油词
2015/07/18 职场文书
机关单位2016年法制宣传日活动总结
2016/04/01 职场文书
golang中切片copy复制和等号复制的区别介绍
2021/04/27 Golang
Django使用echarts进行可视化展示的实践
2021/06/10 Python
OpenCV实现反阈值二值化
2021/11/17 Java/Android