[万字逐步详解]使用 webpack-dev-server + ESLint 配置 vue 项目的开发环境
在上一篇 [万字逐步详解]使用 webpack 打包 vue 项目(基础生产环境) 中,一步一步的完成了基础生产环境的配置,这里就是对开发环境的配置进行实现了。
本次会使用到的依赖只有 webpack-dev-server 和 ESLint 相关的插件,这里会用 webpack-dev-server 去进行开发环境的配置,并且实现以下功能:
使用 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,显示的代码是不可读的:
如果是开发公共组件,在有些时候,直接提供打包好的 bundle 对其他的使用这个开源库的用户来说,就会有阅读困难的问题。所以,这里会加上添加 source map 的功能。
配置是内置的,只需要添加一行新的配置即可:
module.exports = {
devtool: 'eval-cheap-module-source-map',
};
长试运行一下 npm run build 之后,就会看到打包出了一个新的文件:
在使用 Live Server 启用了一个服务器后,再去打开打包后的 index.html,会有下面这样的提示跳出来:
这就说明系统已经能够找到对应的源码,报错就可以直接定位到源码报错的地方。
选择 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';
import Error from './components/Error.vue';
浏览器中的报错结果如下:
注意最后的 App.vue:formatted:11 这段,已经很好的提示错误所在了。
其实这里是强行刷新后产生的报错结果,出现报错的情况下 webpack 会自动停止热刷新,一直到错误修复为止。相似的错误信息也会出现在开启服务器的终端上。
HRM 的开启
这里使用的是 webpack5,一定要确认版本,v4 和 v5 之间配置的差别似乎不太一样,二者的配置不一定能通用。
HRM,即 Hot Module Replacement,即模块热替换,也就是只编译更新过的模块的功能。
之前的配置,即:
module.exports = {
target: 'web',
devServer: {
hot: true,
},
};
其实已经开启了 HMR 和 Live Reloading:
所以这一步相当于在之前的配置中已经完成了。
验证 HMR 效果
已知目录结构是这样的:
并且依赖关系是: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
这里可以看到两组对比:
接下来还原 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.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
这已经可以证明模块热替换的功能已经实现了。
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 一路上报错……还挺多的
npm install eslint --save-dev
Usage
npm install eslint-webpack-plugin --save-dev
npm install eslint-plugin-vue --save-dev
npm install babel-eslint --save-dev
这些都安装完了之后,报错终于停止了,可以开始配置工作了。 -
配置文件 这一步还蛮烦的,一点一点来。
-
初始化 eslint 执行命令 npx eslint --init C:\assignment\front\lagoufed-e-task\part2\fed-e-task-02-02\code\vue-app-base>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: [
'plugin:vue/recommended',
'airbnb-base',
],
rules: {
'linebreak-style': [
'error',
process.platform === 'win32' ? 'windows' : 'unix',
],
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/max-attributes-per-line': [
'warn',
{
singleline: 3,
multiline: {
max: 1,
allowFirstLine: false,
},
},
],
'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 运行失败——所有的错误异常都显示了,这只是程序的正常处理逻辑。
|