彻底理解并解决 'webpack' 不是内部或外部命令,也不是可运行的程序或批处理文件(转)
彻底理解并解决 'webpack' 不是内部或外部命令,也不是可运行的程序或批处理文件
一、问题描述
在安装 npm 包模块的时候,不管是 Webpack 、React 还是 Vue,都需要全局安装,即执行 npm install webpack -g 或者 npm install @vue/cli -g,否则在使用命令(比如查看版本,webpack -v)的时候就会报错 ‘webpack’ 或者 ‘vue’ 不是内部或外部命令,也不是可运行的程序或批处理文件,很多人都是靠全局安装去解决这个问题,那么你知道为什么全局安装可以解决这个问题,或者有没有其他更优的方案吗。今天就给大家分享一下出现这个问题的原因及更好的解决办法。
二、分析原因
在解释这个原因之前先普及一下基本知识。
- 在 windows 环境下执行命令是通过 cmd 文件。比如查看 webpack 版本,执行 webpack -v 命令。那么必定有个 webpack.cmd 文件。如果没有就会报以上错误。( ‘webpack’ 不是内部或外部命令,也不是可运行的程序或批处理文件)。
- cmd 执行文件中文件目录格式为"%~dp0…\webpack\bin\webpack.js"。
其中 “%~dp0” 表示该 cmd 文件所在的当前目录。"…\ "表示上级目录。 - 举例说明以上两点。
有个文件夹为 a,
在 a 目录下有个文件夹 b 和 index.js。
在 b 目录下有个 index.js 和 test.cmd 执行文件,
那么在 b 目录下输入 test 命令就会执行 a 目录的 index.js 的内容。
注意:
a 目录下面有 b 和 index.js ,即 b 和 index.js 是同级目录
b 目录下面也有 index.js 及 test.cmd
a 目标下的 index.js 内容如下:
console.log('hello world a');
- 1
b 目录下的 index.js 内容如下:
console.log('hello world b');
- 1
test.cmd 内容如下:
node "%~dp0\..\index.js" %*
- 1
在当前根目录,也就是 a 目录下执行 test 命令,出现如下报错
因为 a 目录下并没有 test.cmd 文件,而 test.cmd 文件是在 b 目录下。所以切换到 b 目录下执行 test 命令,打印出 ‘hello world a’
那么如果我们想打印出 ‘hello world b’ 怎么办昵,就需要分析一下该 index.js 文件的路径了,可以看到该文件直接在 a 目录下。所以需要修改 test.cmd 文件的执行文件(需要执行的那个 js)路径为
node "%~dp0\index.js" %*
- 1
修改后再次执行 test 得到结果
看到这里就不难猜出报以上错误的原因:
- 在需要执行命令的目录下(比如项目目录)没有 cmd 执行文件。
- 需要确保 cmd 执行文件中要执行的 js 文件路径无误。
三、解决方案
有了以上知识的普及,现在我们定位到具体的项目中。如果没有全局安装模块,只是将模块安装在了当前目录下,而我们执行命令的时候,却总是在项目目录下执行。我们的项目目录下并没有 cmd 执行文件。所以就报了以上错误。现在就基于以上分析的没有 cmd 文件和要执行的文件路径有误这两点说说几种解决方案。
1. 全局安装模块
这个是众所周知的解决方案,基于以上的知识点我们再来分析能解决这个问题的原因。
- 首先执行 npm get prefix 命令得到 npm 的安装路径。一般在C:\Users\xpwu\AppData\Roaming\npm。打开该目录,只要是全局安装的模块,在该目录下都会有相应的 cmd 执行文件,所以在这里我们可以看到webpack.cmd。因此不会报 ‘webpack’ 不是内部或外部命令,也不是可运行的程序或批处理文件的错误。
- 打开 webpack.cmd 文件,判断如果当前目录下有 node.exe 程序,就使用 当前目录下的 node.exe 程序执行文件,否则使用环境变量中的 node.exe 程序来执行文件,显然不会将 node 安装在当前目录下,而是使用环境变量。
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\node_modules\webpack\bin\webpack.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\node_modules\webpack\bin\webpack.js" %*
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 上文有提到 “%~dp0” 表示当前路径,这里的当前路径指的就是C:\Users\xpwu\AppData\Roaming\npm。因为 webpack.cmd 文件就在这个目录下。所以该路径 “%~dp0\node_modules\webpack\bin\webpack.js”,指的就是C:\Users\xpwu\AppData\Roaming\npm\node_modules\webpack\bin\webpack.js。这个文件下的 webpack.js 是存在的,路径没有任何问题。所以全局安装模块满足了1.有 cmd 执行文件;2.要执行的文件路径是正确的这两个条件。
2. 在 cmd 执行文件下运行命令
- 通过 npm install webpack --save 命令把模块安装到项目路径下之后,在项目路径下就会有 node_modules 文件夹,而在该文件夹下就可以找到该模块,是否全局安装可参考文章 npm 中 --save 与 --save-dev 的区别 。
- 细心一点的童鞋可能知道,在 node_modules 文件夹下有个 .bin 文件夹。在该 .bin 文件夹下有 webpack.cmd 文件。
用编辑器打开该 cmd 文件,内容如下:
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\webpack\bin\webpack.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\webpack\bin\webpack.js" %*
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
再啰嗦一下,“%~dp0” 表示当前路径,也就是 .bin 目录,"…\ "表示上级项目,也就是 node_modules 目录,所以该文件路径也是正确的。问题就是每次只能在 项目路径/node_modules/.bin 目录下去使用 webpack 命令了。而我们一般情况下习惯直接在项目目录下去使用命令,那应该怎么做昵?
3.添加 cmd 文件,修改对应路径
- 聪明的你肯定已经猜到,既然要在项目根目录下执行命令,那么就在项目根目录下添加一个 cmd 文件不就行了吗。
- 真是这样吗?哈哈,对自己有点信心,的确如此,需要注意的是满足了第一个条件,第二个条件,执行文件路径是否正确也是需要考虑的,我们把全局环境下的 webpack.cmd 文件或者 .bin 目录下的 webpack.cmd 文件复制一份到项目目录下都可以,甚至自己手写也行,这里我们就以 .bin 目录下的 webpack.cmd 文件复制一份到项目目录下为例。
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\webpack\bin\webpack.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\webpack\bin\webpack.js" %*
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
那么这里的 “%~dp0"指的就是项目目录了,”…" 就到项目目录的上一级目录了,自然不是我们想要的,很显然项目目录下的 webpack.js 文件路径应该是 项目目录/node_modules/webpack/bin/webpack.js。(可能有些人的目录不一定是我这样的,你只需要找到你自己项目下的 webpack.js 文件路径即可)。那么这里就只需要把 “…” 改成 “node_modules” 就是 webpack.js 的正确路径了。即
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\node_modules\webpack\bin\webpack.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\node_modules\webpack\bin\webpack.js" %*
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其实这跟全局下的 cmd 文件路径是一样的,所以如果从全局下复制过来的 webpack.cmd 文件就不用做任何修改了。
四、方案对比
以上3种方案中各有利弊,如果是全局安装,一般情况下肯定也会在项目目录下安装一份,这将造成每个模块都会安装两遍的内存消耗;而只在项目目录下安装并使用以上说的第二种方案每次都在 .bin 目录下执行命令无疑比较麻烦;所以第三种方案,直接复制一份 cmd 文件到项目目录下并修改相应的文件执行路径是最优方案。