使用 webpack 搭建 vue 开发环境(三)


title: 使用 webpack 搭建 vue 开发环境(三)
date: 2020-10-27
sidebar: auto
categories:

  • 前端
    tags:
  • webpack

刚好断更一个月~~。不过我还是回来了!

老规矩,代码放在 码云@Jioho/webpack_config v0.0.3 分支,搞定后合并到 master

根据上次留下的锅,看下这次需要解决什么问题

  • 当前项目的热更新是属于哪种热更新?
  • 实现一个简单的多入口

本来还有 实现自动获取入口(重头戏)添加代码运行后的 URL 输出的。可是在写完多入口后,发现踩坑好多,已经记录了好多,这 2 个就继续放到下一个系列把~

小插曲

今天重新运行项目发现了一条红色提示

作为强迫症的我绝对不能忍的,这个提示意思也很清楚了,在 dev 模式下。不能使用 chunkhash 或者 contenthash作为 js 的哈希值。而我们 dev 模式下的 output输出不知道啥时候被我改错了把~造成了这样子,所以改一下配置:

  • webpack.dev.js
module.exports = {
    
    
  // ...
  output: {
    
    
    + filename: '[name].[hash].js',
    - filename: '[name].[contenthash].js',
    publicPath: '/',
    path: DIST
  }
  // ...
}

至于为什么开发环境的 js 不能用 contenthash 呢?其实也很简单,contenthash 是根据内容修改才重新生成的 hash 值,而 webpack 的宗旨就是万事万物最后都交给 js 处理,包括引入 css/引入其他内容
如果这时候我们只改动了 css。理应 js 要重新加载新的 css,可是没检测到我们改写的 js 内容变化,所以 contenthash 就不会变,自然也不能引入新的 css。

改了之后重新运行就 OK 了

当前项目的热更新是属于哪种热更新?

第一种 WDM

::: tip WDM
webpack-dev-middleware 简称 WDM
编辑器修改了内容,通知浏览器,就是这个中间件发挥的作用
:::

看一张 gif 效果

  1. 我们在输入框输入了一段文本
  2. 修改了 css 代码
  3. (我没按 F5 刷新)是 webpack 的热更新了
  4. 留意文本框的字!!没了!!

留意看几个地方:

  • network 面板的请求
  • console 控制台输出
  • 输入框的字
  • 文本颜色的变化

可以看到,触发热更新的步骤,
然后控制台先输出了

同时在请求 localhost:8080 注意这时候请求还没成功
在请求成功后,浏览器自动刷新了(因为控制台文本被清空了)
接着就请求 main.js,请求新的 css,最后在创建一个新的 websocket 来保持下一次的连接

看上去好像挺不错了,免去了人工刷新的一步,改了就能看到效果了,可是如果你开发的是表单页面,一大堆的输入框表单,输入了半天,最后发现 js/css 好像写错了一点东西,一改,刷新,全没了,心态都崩了,所以这种并不是最好的。

第二种 HMR

::: tip HMR
Hot Module Replacement(以下简称 HMR
是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下就能够对应用进行更新
:::

看下啥特性能让程序猿那么兴奋

修改代码,实现 HRM

重新整理下 PATH.js

因为这次要用的目录还挺多的,重新看了下 PATH.js 文件,声明的变量挺多的,而且导出还得重新在复制一次,相对来说比较麻烦~

  • build/PATH.js 修改前:
const path = require('path')
const ROOT = path.resolve(__dirname, '../')
const DIST = path.resolve(ROOT, 'dist')

const tmp_main = path.resolve(ROOT, 'src/main.js')
const TEMPLATE_HTML = path.resolve(ROOT, 'public/index.html')

module.exports = {
    
    
  ROOT,
  DIST,
  TEMPLATE_HTML,
  tmp_main
}
  • build/PATH.js 修改后:

改了一下,把路径都统一放入 PATH 后导出,而且多加了几个配置文件的路径

const path = require('path')

const ROOT = path.resolve(__dirname, '../')

const PATH = {
    
    
  ROOT: ROOT,
  DIST: path.resolve(ROOT, 'dist'),
  tmp_main: path.resolve(ROOT, 'src/main.js'),
  TEMPLATE_HTML: path.resolve(ROOT, 'public/index.html'),
  BASE_CONFIG: path.resolve(ROOT, 'build/webpack.base.js'),
  DEV_CONFIG: path.resolve(ROOT, 'build/webpack.dev.js'),
  PROD_CONFIG: path.resolve(ROOT, 'build/webpack.prod.js')
}

module.exports = PATH

理一下流程

我们需要自己用 express 运行一个服务(这也是为后面做铺垫的),然后使用 webpack 的 dev 模式,加热更新,让我们修改的文件能局部替换到浏览器中,尽可能不要全部刷新页面(只是尽可能,因为总有些情况会全部刷新的~)

不然不用 webpack-dev-serve 了。那我们运行的命令也得改一下,不能直接用 webpack 的命令了,而是自己用 node 运行,下面都会说到

安装依赖

要实现这个功能,我们需要安装 3 个包:expresswebpack-hot-middlewarewebpack-dev-middleware

webpack-dev-serve 可以卸载了,使用 npm uninstall webpack-dev-serve
当然也可以留到最后实现了才卸载,看个人把!

npm i express webpack-hot-middleware webpack-dev-middleware -D
  • express 是为了给我们运行服务的,比如跑在指定端口
  • webpack-dev-middleware 运行 webpack 的中间件
  • webpack-hot-middleware 今天的主角,热更新就是他了

准备入口文件

新建: build/dev-server.js

const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const {
    
     DEV_CONFIG } = require('./PATH')

const app = express()
const config = require(DEV_CONFIG)

const compiler = webpack(config)

const devMiddleware = webpackDevMiddleware(compiler, {
    
    
  publicPath: config.output.publicPath,
  hot: true, // 开启热更新,浏览器自动刷新
  quiet: true,
  inline: true // 必须开启
})

// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
app.use(devMiddleware)

// 热更新
app.use(
  webpackHotMiddleware(compiler, {
    
    
    log: () => console.log
  })
)

app.listen('8080', function() {
    
    
  console.log('> Listening at 8080 !\n')
})

要想达到热替换的效果,dev 模式也得添加一个 webpack 内置的插件,所以修改

  • webpack.dev.js

其中要告诉 devServer 我们项目的路径位置(有没有都没关系,有些教程说要有)
然后就是添加 plugins ,添加一个 webpack.HotModuleReplacementPlugin 这个特别重要

const baseConfig = require('./webpack.base')
const {
    
     merge } = require('webpack-merge')
const path = require('path')
const {
    
     DIST } = require('./PATH')
const webpack = require('webpack')

module.exports = merge(baseConfig, {
    
    
  mode: 'development',
  output: {
    
    
    filename: '[name].[hash].js',
    publicPath: '/',
    path: DIST
  },
  devServer: {
    
    
    contentBase: DIST //指定需要提供给本地服务的内容的路径,默认加载index.html文件,可根据需要修改
  },
  plugins: [new webpack.HotModuleReplacementPlugin()] // 新增了这一行
})

有了 webpack.HotModuleReplacementPlugin 插件的支持,入口文件也得支持一下

  • webpack.base.js
module.exports = {
    
    
  // entry: tmp_main,
  entry: {
    
     main: ['webpack-hot-middleware/client', tmp_main] } // 添加 webpack-hot-middleware/client 标识这是要热更新的文件
}

PS:等下记得把 ‘webpack-hot-middleware/client’ 加一个判断,只有开发环境才打开,不然打包后的文件会出错的
main: devMode ? [‘webpack-hot-middleware/client’, tmp_main] : [tmp_main]
判断 devMode 在下面有讲到~


修改:package.json 把之前的 serve 命令改成是 dev,把 serve 留给现在的命令,使用 node 运行
注意没有后面的 --mode development

  "scripts": {
    
    
    "dev": "webpack-dev-server --config build/webpack.dev.js --mode development",
    "serve": "node build/dev-server.js",
    "build": "webpack --config build/webpack.prod.js --mode production"
  },

OK,运行试下:

可能会遇到这种错误

GET http://localhost:8080/__webpack_hmr net::ERR_CONNECTION_RESET 200 (OK)
Cannot find update
(Probably because of restarting the server)

这种情况可能就真的只是网络问题了

BUT!!下面这种情况一定会遇到(这个问题我调试了一下午!)

HRM 第一次热更新不起作用 / [HMR] Nothing hot updated.

如果按上面的步骤来写,很大几率会遇到这个问题,就是 webpack 启动后第一次,热更新总是失败的,可是你第二次热更新,他就 OK 了,具体表现如下:

  • 这是网络不好/链接错误的表现,通常来说重新跑一下服务,或者刷新几次就好了

  • 第二种, App 显示已经 update ,可是页面没更新,第二次在刷新,他又 OK 了?

来看我的一段不权威分析(乱说一通)

第一次不成功的时候,有加载一个 less/css 的更新,第二次就没了(后面几次也没有了)。那现在要么就打个断点,从 console 的面板,进到对应的更新源码看看 那就直接试下把 less 的热更新加上!

修改 less 配置,支持热更新

不用 webpack-dev-server 的话,每种类型的热更新,尤其是涉及需要编译的内容,都得我们自己写配置支持
less 的配置是在 webpack.base.js

  • webpack.base.js

判断只有在开发模式下才打开 hrmreloadAll

const devMode = process.env.NODE_ENV === 'development' // 是否是开发模式

module.exports = {
    
    
  // ...
  module: {
    
    
    rules: [
      {
    
    
        test: /\.(less|css)$/,
        use: [
          {
    
    
            loader: MiniCssExtractPlugin.loader,
            options: {
    
    
              publicPath: '../',
+             // only enable hot in development
+             hmr: devMode, // 添加hrm支持
+             // if hmr does not work, this is a forceful method.
+             reloadAll: devMode // 添加热更新
            }
          },
          {
    
     loader: 'css-loader', options: {
    
     esModule: false } },
          'postcss-loader',
          'less-loader'
        ]
      }
    ]
  }
  // ...
}

是不是加了就生效了呢? No,还漏了一步 process.env.NODE_ENV 是需要指定的,在我们启动环境的时候,就需要指定当前 node 的运行环境,所以呢,得改一下 package.json 。用 cross-env 来跨平台的设置环境

npm i cross-env -D
  • package.json

把 serve 命令改掉,添加运行的环境,然后在执行 node 去运行我们的文件

"serve": "cross-env NODE_ENV=development node build/dev-server.js",

还有一个要注意的地方
如果项目的 css 用了 hash 或者 其他哈希的话,在 dev 热更新模式下,就记得不要用了!!不然 css 也会更新失败
所以开发环境配置,在改一下:添加了 css 插件,不过只输出名称,不输出哈希值了

  • webpack.dev.js
const {
    
     merge } = require('webpack-merge')
const {
    
     DIST, BASE_CONFIG } = require('./PATH')
const baseConfig = require(BASE_CONFIG)
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = merge(baseConfig, {
    
    
  mode: 'development',
  output: {
    
    
    filename: 'js/[name].[hash].js',
    publicPath: '/',
    path: DIST
  },
  devServer: {
    
    
    contentBase: DIST //指定需要提供给本地服务的内容的路径,默认加载index.html文件,可根据需要修改
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new MiniCssExtractPlugin({
    
    
      filename: 'css/[name].css'
    })
  ]
})

重新运行项目,效果:

忘记录制 css 修改的效果了,可以自己试下

细心地可能发现了,我又在输入框输入内容了,这次的热更新,会比之前的 WDM 好一丢丢,起码我们修改标签内的文案/修改颜色,只是局部刷新
我也试了如果不是修改生命周期的 JS,通常也会局部刷新上去,但是如果修改了生命周期/改了布局,那可能 webpack 处理不了的,就直接页面刷新了,总的来说体验还是好了一点的

我是一段小尾巴

在排查问题的时候,发现有几个地方写的不咋的,比如打包后的文件,html 和 js 文件都在同级了!我们应该把 js 统一下,把 css 也统一下,img 也统一下
所以分别在 webpack.dev.js webpack.base.js webpack.prod.js 改一下 output 的 filename。前面多加一个 js/
这会不会影响 publicPath 呢?不会的~亲测有效

第二个就是统一下 PATH,我们在前面把 dev 和 prod,base 的配置都写到了 PATH 里面了,所以在后面几个文件我们都统一用 PATH 来作为路径变量(具体的可以自己找一找改一改,或者到时候看 v0.0.3 的代码)

实现一个简单的多入口

啰嗦一下

看到了这一步,代表基本的打包(vue+less)是可以了,然后在稍微的锦上添花(HRM)也安排了一下,到目前为止我们都是只在修改App.vue一个文件,可是到了实际的项目中,几百个路由是不在话下的(曾经做过一个项目就是 450+个路由,运行一次基本就热更新都得等 30s)

写这个 webpack 很大一部分原因是想把页面使用单页面的模式,可是每个页面都有独立的 HTML,这样有利于以后哪怕要优化 SEO,我们也可以把 SEO 部分内容放 html,实际上我们只控制 #app 节点来显示页面内容,让爬虫抓页面隐藏部分的内容来实现 SEO

还有一个好处就是,做成多入口后,之前的项目就可以进行升级,450+路由页面,划分几十个模块,每个模块几个-10 几个页面(这种热更新还是可以支持的),以后项目就按模块来更新,不用改了一行代码都要把整套项目重新打包~

话不多说,现在开始

看下当前的目录结构

.
|-- README.en.md
|-- README.md
|-- build
|   |-- PATH.js
|   |-- dev-server.js
|   |-- webpack.base.js
|   |-- webpack.dev.js
|   `-- webpack.prod.js
|-- package-lock.json
|-- package.json
|-- postcss.config.js
|-- public
|   `-- index.html
|-- src
|   |-- App.vue
|   |-- assets
|   |   |-- css
|   |   |   `-- common.less
|   |   `-- image
|   |       `-- avatar.jpg
|   `-- main.js
`-- testconfig.js

在 src 文件夹下, App 和 main.js 同级,并且都在 src 下,那原先已经有的就不动他了

我们新建 2 个模块(商品模块(goods)和个人中心(user)模块),并且在 user 模块下,在模拟一下复杂的 URL 嵌套的情况!

内容特别简单, index.js 引入我们的对应的 vue 文件(其实就是 main.js 的作用),然后 vue 文件简单写点内容(我就直接把 App.vue 的内容扣过来了,顺便一起测试引入 less,引入图片的问题),其余几个文件同理

搬 App.vue 文件的时候记得项目引入路径的问题,之前都是 ./,迁移目录后记得改成对应的 ../ 甚至是 ../../

修改 webpack.base.js

敲黑板,这里很多知识点和细节!!

文件结构已经有了,那么下一步就是添加 webpack 的多入口。我是特意模拟了多入口,多入口文件嵌套的情况!

先说第一批知识点:

  1. 多入口的 webpack,entry 对象中,键值(也就是我们的模块名),在配置中可以用[name] 来写
  2. 如果引入的对应资源/文件名 有 / ,假设起的入口名字是 user/index 那么到时候输出的文件,就会是 user 文件夹下的 index.js 文件
  3. 还记得教程第二章中说的 webpack/vue-cli 中的 publicPath 区别配置-css-样式分离 要知道,我们图片和 css 的配置,只能相差一个文件夹。也就是只能用 ../ 。配合知识点 2,如果我们滥用 user/index 这种名称为文件起名字,我们的图片很容易就找不到

综合考虑,于是就有了下面的配置:

  • webpack.base.js

上个截图比较直观:

知识点小总结:

普通模块,我们还是用了文件本身的名称,比如user,goods之类的。像嵌套的文件夹,user/setting 的文件夹,我们在入口是用了 user_setting。但是输出的 html 中fileName 还是用回了 /。在 chunks 继续保留了 user_setting 。因为 chunks 是需要和入口文件相对应的

而且留意看 user/setting 的配置中,还需要额外的 publicPath 为 ../ 也是因为文件夹嵌套引发的问题

如果太难理解,就先继续看下去把,等下项目运行起来就知道到底为什么要这么做了!

知识点 2

为什么我这里写 ./src/xxxx 而不用 PATH?而且为什么是 ./src 不是 ../src??

  1. 因为这只是临时的演示,等下就搞动态入口配置了,就不想去写 PATH 了

  2. 为什么是 ./src 。这是一个很重要的知识点,webpack 运行后,他的目录并不是根据当前的文件目录,而且又从根目录开始找,所以 ../src 会让 webpack 找不到文件在哪里(是不是很拗口,所以我干脆直接写 PATH,里面统一我们的项目路径,一了百了~)

  • webpack.base.js
module.exports = {
    
    
  entry: {
    
    
    gooods: devMode ? ['webpack-hot-middleware/client', './src/goods/index.js'] : ['./src/goods/index.js'],
    user: devMode ? ['webpack-hot-middleware/client', './src/user/index.js'] : ['./src/user/index.js'],
    user_setting: devMode
      ? ['webpack-hot-middleware/client', './src/user/setting/index.js']
      : ['./src/user/setting/index.js']
  },
  plugins: [
    new HtmlWebpackPlugins({
    
    
      filename: 'goods.html',
      template: TEMPLATE_HTML,
      chunks: ['goods']
    }),
    new HtmlWebpackPlugins({
    
    
      filename: 'user.html',
      template: TEMPLATE_HTML,
      chunks: ['user']
    }),
    new HtmlWebpackPlugins({
    
    
      filename: 'user/setting.html',
      template: TEMPLATE_HTML,
      publicPath: '../',
      chunks: ['user_setting']
    })
  ]
}

运行后, localhost:8080 应该是访问不了了,因为 webpack 做了多入口后,又没有 index 入口,没有了默认入口就会报 404 。可以自己建一个 index 入口,那么接下来直接访问 localhost:8080/user.htmllocalhost:8080/goods.html 或者 localhost:8080/user/setting.html

看 setting 的路由,是 user/setting.html 这就是为什么在 html 的 fileName 我们需要写 / ,就是为了符合我们的 URL 地址和文件路径的映射!


本地运行没有问题,打一个包试下

回顾下刚才说的东西

  1. user/setting.html 的 publicPath 为什么要 …/?
    根据打包后的目录来看,user/setting.html 想要引入 JS 资源,是不是得 ../js/。而普通的一层的页面,只需要 ./js/ 就能找到 JS 了
  1. 为什么入口文件,要把/改成_,即原本是 user/setting 的,要改成 user_setting
    因为 css 的文件打包后 [name] 配置读取的就是入口文件配置的名字,如果直接使用 user/setting。那么 css 生成的文件也会嵌套多层,到时候如果在 css 里面还引入了图片,那就真的找不到图片了,具体可以自己改一下入口文件试一下~

到这里,我们已经实现了一个多页面的 vue,而且摆脱了 # 哈希路由!!成功实现了一个多入口的单页面应用

最后

webpack 踩坑系列(三)也结束了~。摸索了一下热更新的区别,自己实现了一个 HRM。搭建了单页面多入口的项目雏形

下期预告:

  • 公共模块一起打包
  • 实现自动获取入口(重头戏)
  • 添加代码运行后的 URL 输出
  • 自动获取端口号

欢迎围观新的地址:使用 webpack 搭建 vue 开发环境(三)

猜你喜欢

转载自blog.csdn.net/Jioho_chen/article/details/109316799