[万字逐步详解]使用 webpack-dev-server + ESLint 配置 vue 项目的开发环境
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[万字逐步详解]使用 webpack-dev-server + ESLint 配置 vue 项目的开发环境相关的知识,希望对你有一定的参考价值。
[万字逐步详解]使用 webpack-dev-server + ESLint 配置 vue 项目的开发环境
在上一篇 [万字逐步详解]使用 webpack 打包 vue 项目(基础生产环境) 中,一步一步的完成了基础生产环境的配置,这里就是对开发环境的配置进行实现了。
本次会使用到的依赖只有 webpack-dev-server 和 ESLint 相关的插件,这里会用 webpack-dev-server 去进行开发环境的配置,并且配合 ESLint 实现以下功能:
-
开发环境的配置
-
Source Map 的添加
-
HRM 的开启
HRM 的开启是为了能够更加有效的进行开发,只更新被修改的模块,而不会将整个项目重新打包
-
ESLint 的设置
使用 webpack-dev-server 开发服务器的部署
目前为止,我本地的热部署使用的是 VSCode 提供的插件:Live Server。但是,并不是所有的开发团队成员都会使用同样的开发软件,同样,也不能保证所有的成员都有一样的插件。因此,开发一个具有相同功能的服务器就是一件必须的事情了。
这里会使用 webpack-dev-server
去解决这个问题,webpack-dev-server 集成了一些自动化的功能,因此可以搞定自动编译、自动监听、自动刷新浏览器的功能。
-
下载安装插件
npm install webpack-dev-server --save-dev
-
修改配置
webpack-dev-server 会提供一个 dev-server 的 cli 程序,通过这个程序就可以开启一个服务器。这里会通过将这个命令写到 package.json 里面的 scripts 去节省一些事儿:
{ "scripts": { "serve": "webpack serve --mode=development --open" } }
其他的配置属性不变,只新增一个 serve 命令即可。
现在开始配置 webpack.config.js,注意,为了提升效率, webpack-dev-server 是将文件保存在缓存中,而不会直接在磁盘中进行重写。
在配置中其实可以将
publicPath: "./"
这一行注释掉,一来其实并不影响 production 的打包,二来没有办法正确的将所有的数据缓存起来,会影响 dev server 的情况。具体的配置如下:
module.exports = { // 这个还蛮重要的,不然不知道为什么热更新会失败 target: 'web', // 省略其他配置 devServer: { // 指定端口 port: 9000, // 命令行中会显示打包的进度 progress: true, // 开启热更新 hot: true, // 是个很有趣的特性 historyApiFallback: true, // 添加静态资源的引用,可以用数组 // 这样原本没有被打包进去的内容也可以正确被引用 contentBase: path.join(__dirname, 'public'), }, // 省略其他配置 };
在配置完了 contentBase 之后,也就代表着可以在开发环境中取消使用 copy-plugin,这样在针对比较大的文件的时候——尤其是有些图片确实会比较大,就能够省去不少的时间。
historyApiFallback 是一个还挺有趣的特性,它主要是提供了一个路径让服务器去调用,官方文档是这样说的:
module.exports = { //... devServer: { historyApiFallback: { rewrites: [ { from: /^\\/$/, to: '/views/landing.html' }, { from: /^\\/subpage/, to: '/views/subpage.html' }, { from: /./, to: '/views/404.html' }, ], }, }, };
也就是说,当访问的路径与指定的路径 match 的时候,就会跳转到 to 中指定的页面。
-
关于 html-webpack-plugin
这个之前在做 production 环境配置的时候就已经折腾好了,这里不多赘述,这部分在 [万字逐步详解]使用 webpack 打包 vue 项目(基础生产环境) 已经有详细的配置说明了。
-
修改点什么试试看结果
从命令行就能看到,保存过后就会重新编译,这样热更新就完成了:
最终效果:
添加 source map
添加 source map 是为了能够方便 debug,现在打开 chrome 中的 source,显示的代码是不可读的:
![](https://image.cha138.com/20210729/b28a4c4c62a143578898bb6fbf6f971d.jpg)
如果是开发公共组件,在有些时候,直接提供打包好的 bundle 对其他的使用这个开源库的用户来说,就会有阅读困难的问题。所以,这里会加上添加 source map 的功能。
配置是内置的,只需要添加一行新的配置即可:
module.exports = {
// 其他不变
devtool: 'eval-cheap-module-source-map',
};
长试运行一下 npm run build
之后,就会看到打包出了一个新的文件:
![](https://image.cha138.com/20210729/d79116abfc294ce4a6571c737fedbedf.jpg)
在使用 Live Server 启用了一个服务器后,再去打开打包后的 index.html,会有下面这样的提示跳出来:
![](https://image.cha138.com/20210729/e80715f214294ec19ce85f72f8b6ed32.jpg)
这就说明系统已经能够找到对应的源码,报错就可以直接定位到源码报错的地方。
选择 cheap-module-source-map 的原因很简单,它会将 webpack 打包好的模块按照 module 还原成源码,并且能够准确定位到报错的行。在正常的情况下,为了保证代码的可读性,每行代码在 80-120 个字符范围内的情况下,定位到行就已经够了。
另外,对于 React/Vue 来说,原生的 javascript 和二者的语法糖差异较大,很难从编译过后的 JavaScript 直接在脑中转换成对应框架的代码,所以这也是需要使用源码的原因。同理,在生产环境就应该使用 nosources/none 的选项,这样别人就比较难从编译过后的 JavaScript 了解到源码。
下面是来自 webpack-Devtool 官网上列举的差别:
devtool | performance | production | quality | comment |
---|---|---|---|---|
(none) | build: fastest rebuild: fastest | yes | bundle | Recommended choice for production builds with maximum performance. |
eval | build: fast rebuild: fastest | no | generated | Recommended choice for development builds with maximum performance. |
eval-cheap-source-map | build: ok rebuild: fast | no | transformed | Tradeoff choice for development builds. |
eval-cheap-module-source-map | build: slow rebuild: fast | no | original lines | Tradeoff choice for development builds. |
eval-source-map | build: slowest rebuild: ok | no | original | Recommended choice for development builds with high quality SourceMaps. |
cheap-source-map | build: ok rebuild: slow | no | transformed | |
cheap-module-source-map | build: slow rebuild: slow | no | original lines | |
source-map | build: slowest rebuild: slowest | yes | original | Recommended choice for production builds with high quality SourceMaps. |
inline-cheap-source-map | build: ok rebuild: slow | no | transformed | |
inline-cheap-module-source-map | build: slow rebuild: slow | no | original lines | |
inline-source-map | build: slowest rebuild: slowest | no | original | Possible choice when publishing a single file |
eval-nosources-cheap-source-map | build: ok rebuild: fast | no | transformed | source code not included |
eval-nosources-cheap-module-source-map | build: slow rebuild: fast | no | original lines | source code not included |
eval-nosources-source-map | build: slowest rebuild: ok | no | original | source code not included |
inline-nosources-cheap-source-map | build: ok rebuild: slow | no | transformed | source code not included |
inline-nosources-cheap-module-source-map | build: slow rebuild: slow | no | original lines | source code not included |
inline-nosources-source-map | build: slowest rebuild: slowest | no | original | source code not included |
nosources-cheap-source-map | build: ok rebuild: slow | no | transformed | source code not included |
nosources-cheap-module-source-map | build: slow rebuild: slow | no | original lines | source code not included |
nosources-source-map | build: slowest rebuild: slowest | yes | original | source code not included |
hidden-nosources-cheap-source-map | build: ok rebuild: slow | no | transformed | no reference, source code not included |
hidden-nosources-cheap-module-source-map | build: slow rebuild: slow | no | original lines | no reference, source code not included |
hidden-nosources-source-map | build: slowest rebuild: slowest | yes | original | no reference, source code not included |
hidden-cheap-source-map | build: ok rebuild: slow | no | transformed | no reference |
hidden-cheap-module-source-map | build: slow rebuild: slow | no | original lines | no reference |
hidden-source-map | build: slowest rebuild: slowest | yes | original | no reference. Possible choice when using SourceMap only for error reporting purposes. |
基本上来说,webpack 都已经显示了推荐用于不同情况下的不同环境,推荐使用哪种类型的 devtool 了,这里简单描述下:
-
带有 eval
将模块代码放到 eval 函数中去执行,从而产生独立的作用域,再通过 source-url 去标注文件的路径
只有 eval 的情况下,只能定位报错的文件
-
带有 source-map
这会将代码转译成转换过的代码,可定位到具体的行和列
-
带有 cheap-source-map
这会将代码转译成转换过的代码,代码会定位到行,但是不会定位到列
-
带有 module-source-map
会将代码翻译成原生代码,并不会进行转译。即,未经过 loader 转译过后的代码
-
带有 inline-source-map
将源码嵌入到 module 代码中去,webpack 推荐,这种情况可能在发布独立一个文件时发生
-
hidden
开发模式下看不见源码,但是源码会被一起打包
比较适合用于开源项目
-
nosources
会显示行列信息,但是不会列出源码
比较适合用于生产环境
所以说,正常来说我会选择使用 eval-cheap-module-source-map 作为开发环境,而 nosources/none 作为部署环境的代码。
下面是报错结果,我刻意在 Vue.js 中导入了一个不存在的文件:
import HelloWorld from './components/HelloWorld.vue';
// Error组件不存早,下面在源代码中是第10行
import Error from './components/Error.vue';
浏览器中的报错结果如下:
![](https://image.cha138.com/20210729/64f4a4da8f344507bd24bea4e8ce9665.jpg)
注意最后的 App.vue:formatted:11 这段,已经很好的提示错误所在了。
其实这里是强行刷新后产生的报错结果,出现报错的情况下 webpack 会自动停止热刷新,一直到错误修复为止。相似的错误信息也会出现在开启服务器的终端上。
HRM 的开启
这里使用的是 webpack5,一定要确认版本,v4 和 v5 之间配置的差别似乎不太一样,二者的配置不一定能通用。
HRM,即 Hot Module Replacement,即模块热替换,也就是只编译更新过的模块的功能。
之前的配置,即:
module.exports = {
// 这一行说明运行环境是类似浏览器的环境
target: 'web',
devServer: {
// 开启热更新
hot: true,
},
};
其实已经开启了 HMR 和 Live Reloading:
![](https://image.cha138.com/20210729/353d916b5eff4b7fbe88c64195f57737.jpg)
所以这一步相当于在之前的配置中已经完成了。
验证 HMR 效果
已知目录结构是这样的:
![](https://image.cha138.com/20210729/22ba764550c64f9caa2ea576c128b5cf.jpg)
并且依赖关系是:main 引用 App.vue, App.vue 引用 HelloWorld.vue,所以可以分别修改 main.js, App.vue, 和 HelloWorld.vue 去验证重新打包的大小,借此验证 HMR 是否更新成功。
-
更新 HelloWorld.vue
assets by path *.js 2.54 MiB asset bundle.js 2.53 MiB [emitted] (name: main) asset main.598fea34c0d6bb419535.hot-update.js 17 KiB [emitted] [immutable] [hmr] (name: main) asset index.html 602 bytes [emitted] asset main.598fea34c0d6bb419535.hot-update.json 28 bytes [emitted] [immutable] [hmr] Entrypoint main 2.54 MiB = bundle.js 2.53 MiB main.598fea34c0d6bb419535.hot-update.js 17 KiB cached modules 863 KiB [cached] 264 modules runtime modules 26.8 KiB 14 modules javascript modules 9.46 KiB
这里可以看到两组对比:
-
main.598fea34c0d6bb419535.hot-update.js 17 KiB
热更新的部分的大小是 17KiB
-
cached modules 863 KiB [cached] 264 modules
缓存的大小是 863Kib
接下来还原 Helloworld.vue,保存后,再更新 App.Vue
-
-
更新 App.Vue
assets by path *.js 2.54 MiB asset bundle.js 2.53 MiB [emitted] (name: main) asset main.815ce3af01699b267e86.hot-update.js 10.1 KiB [emitted] [immutable] [hmr] (name: main) asset index.html 602 bytes [emitted] asset main.815ce3af01699b267e86.hot-update.json 28 bytes [emitted] [immutable] [hmr] Entrypoint main 2.54 MiB = bundle.js 2.53 MiB main.815ce3af01699b267e86.hot-update.js 10.1 KiB cached modules 870 KiB [cached] 264 modules
-
main.815ce3af01699b267e86.hot-update.js 10.1 KiB
热更新的部分的大小是 10.1KiB
可以看到热更新的代码少了
-
cached modules 870 KiB [cached] 264 modules
缓存的大小是 870Kib
可以看到缓存的模块多了
继续还原后再给 main.js 加个空格
-
-
更新 main.js
assets by path *.js 2.53 MiB asset bundle.js 2.53 MiB [emitted] (name: main) asset main.fafa37f13c47741d83cd.hot-update.js 2.37 KiB [emitted] [immutable] [hmr] (name: main) asset index.html 602 bytes [emitted] asset main.fafa37f13c47741d83cd.hot-update.json 28 bytes [emitted] [immutable] [hmr] Entrypoint main 2.53 MiB = bundle.js 2.53 MiB main.fafa37f13c47741d83cd.hot-update.js 2.37 KiB cached modules 873 KiB [cached] 267 modules
-
main.fafa37f13c47741d83cd.hot-update.js 2.37 KiB
能看出热更新的部分又少了
-
cached modules 873 KiB
而缓存的模块又多了
-
这已经可以证明模块热替换的功能已经实现了。
ESLint 设置
在 webpack 中,ESLint 的也是通过 loader 进行实现。理论上来说,在 JavaScript 文件被处理之前,应该通过 ESLint 的 loader 对 JavaScript 源码加上该有的信息。
注*:来自 webpack 官网:
The loader
eslint-loader
will be deprecated soon, please use this plugin instead.
所以这里会使用对应的插件完成功能。
注 2*:使用 webpack 插件去运行时,ESLint 部分的运行失败,好像并不会影响项目的运行。
-
安装插件
这里也是根据报错信息来的,如果不是 vue 项目,不需要下载 eslint-plugin-vue
一路上报错……还挺多的
# 要用 ESLint 的 webpack 插件肯定要先安装 ESLint npm install eslint --save-dev Usage # 安装对应的 webpack 插件 npm install eslint-webpack-plugin --save-dev # 安装对应的 vue 版本 npm install eslint-plugin-vue --save-dev # 又一个报错信息,提示需要 babel-eslint npm install babel-eslint --save-dev
这些都安装完了之后,报错终于停止了,可以开始配置工作了。
-
配置文件
这一步还蛮烦的,一点一点来。
-
初始化 eslint
执行命令
npx eslint --init
D:\\vue-webpack>npx eslint --init ? How would you like to use ESLint? ... To check syntax only > To check syntax and find problems To check syntax, find problems, and enforce code style
第一步会有三个选项,第一个只会找语法错误,第二个会找语法错误和问题代码,如未使用的变量、不存在的变量等,第三个就是加上代码风格的检查,包括行太长,缩进之类的问题。
这里选择第三个,也检查代码风格
现在是第二个问题:
? What type of modules does your project use? ... > JavaScript modules (import/export) CommonJS (require/exports) None of these
这里使用的时候 vue,不在二者之间,所以选择 None
然后是第三个问题:
? Which framework does your project use? ... > React Vue.js None of these
这里用的是 Vue,就选 Vue 了
第四个问题:
? Does your project use TypeScript? » No / Yes
这里没有用 TypeScript,选择 No
第五个问题:
? Where does your code run? ... (Press <space> to select, <a> to toggle all, <i> to invert selection) √ Browser √ Node
这里虽然看不清楚,但是 Browser 和 Node 的 √ 的颜色其实不太一样。
是个网页项目,选择 Browser 即可。
第六个问题:
? How would you like to define a style for your project? ... > Use a popular style guide Answer questions about your style Inspect your JavaScript file(s)
第一个选项就是用主流风格,第二个就是它会提示问题,然后自动生成,最后选项就是根据 JavaScript 文件自动生成。
我想躺不想努力了,用市面上现成的风格。
第七个问题:
? Which style guide do you want to follow? ... > Airbnb: https://github.com/airbnb/javascript Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo
可恶……居然没有 Vue 的选项,那就用 Airbnb 吧(React 项目中用 Airbnb 习惯了)。
第八个问题:
? What format do you want your config file to be in? ... > JavaScript YAML JSON
配置文件的类型,盲选 JavaScript。
第九个问题:
Checking peerDependencies of eslint-config-airbnb-base@latest The config that you've selected requires the following dependencies: eslint-plugin-vue@latest eslint-config-airbnb-base@latest eslint@^5.16.0 || ^6.8.0 || ^7.2.0 eslint-plugin-import@^2.22.1 ? Would you like to install them now with npm? » No / Yes
问你要不要安装对应的插件……当然……
-
修改.eslintrc.js
module.exports = { extends: [ // 这里原本是essential,改成了recommended 'plugin:vue/recommended', 'airbnb-base', ], rules: { // 搞定换行符的问题,windows和unix的换行符不一样,所以这个可能会爆出大量的错误,尤其是开发设备不统一的情况下 'linebreak-style': [ 'error', process.platform === 'win32' ? 'windows' : 'unix', ], // Disallow self-closing on HTML void elements 的问题 'vue/html-self-closing': [ 'error', { html: { void: 'always', normal: 'never', component: 'always', }, svg: 'always', math: 'always', }, ], // XXX should be on a new line 问题 'vue/max-attributes-per-line': [ // 修改警告等级 'warn', { // 单行时设置每行的上限为3个 singleline: 3, // 多行的配置 multiline: { // 多行的属性上限 max: 1, // 允许第一行可以设置属性 allowFirstLine: false, }, }, ], // 修改 unused 和 quotes 的警告等级 'no-unused-vars': ['warn'], quotes: ['warn'], }, };
-
-
代码测试
最后使用
npx run src/App.vue
的运行结果:unused var 是我想要看到的结果,single quote 这个引号问题可以搭配 prettier 这种工具去进行自动修正。
同时使用
npm run serve
也能看到 ESLint 抛出异常,这也可以证明 ESLint 的配置已经成功了。 -
对 package.json 的补充
补充一下 lint 脚本:
{ "script": { "build": "webpack --config webpack.prod.js", "lint": "eslint **/*.vue" } }
注*:如果直接运行
npm run lint
,并且文件中有错误信息的话,npm 会抛出错误异常:npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! vue-app-base@0.1.0 lint: `eslint **/*.vue` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the vue-app-base@0.1.0 lint script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
在 eslint 一个 issue 中也有讨论过这个问题:When running eslint with npm script, npm throws error. #7933,杰伦就是,这件事情是正常的。因为程序运行有异常,ESLint 抛出了一场又被 npm 所捕获,所以最终以 1 退出运行,这就会造成报错。
试了一下讨论中说可以这么修改:
"lint": "eslint app/;exit 0"
,最终报错,无法使用,唯一成功的就是修改运行时后的脚本,将运行命令改为:npm run lint -s
,这样的做法其实是 silent(安静) 报错信息,隐藏报错信息而已。如果个人看的不舒服可以使用后者去解决,不过就说一下,这不代表 ESLint 运行失败——所有的错误异常都显示了,这只是程序的正常处理逻辑。
以上是关于[万字逐步详解]使用 webpack-dev-server + ESLint 配置 vue 项目的开发环境的主要内容,如果未能解决你的问题,请参考以下文章
[万字逐步详解]使用 webpack 打包 vue 项目(基础生产环境)