webpack-vue脚手架之Dev篇

820 阅读11分钟

引言

《webpack-vue脚手架之build篇》,在开发的过程中,我们不可能写一下打包一下,看一下效果。或者是某一些环境因素(比如说接口还没开发出来,跨域等)我们可能也没有办法做调试。那么我们这一篇就来跟大家一起探讨一下,如何在自己的脚手架当中加入本地调试所需要的元素,如果在文章中,或者哪一个方面有错误或者想要讨论的,都欢迎在文章的结尾留言 我会尽快回复大家(加班狗不容易!!!!)

开始

经过之前的折腾整个的文件结构已经变成如下情况

.
├── node_modules
├── dist                          // 打包后的文件
│   ├── images
│   │   └── timg.cff62bc.jpeg
│   ├── index.html
│   ├── main.js
│   └── styles.css
├── index.html
├── package-lock.json
├── package.json
├── src
│   ├── app.vue
│   ├── assets
│   │   └── timg.jpeg
│   ├── index.js
│   └── main.js
└── webpack.config.js            // webpack的相关配置

那么之前如果想要知道自己样式,代码的修改情况。首先我们要先运行npm run build进行打包,然后再打开打包文件中的index.html 才可以浏览到对应的修改情况。属实有点麻烦。那么有没有什么办法可以让我们一边进行代码修改,一边实时可以看到自己修改的情况呢? 答案还是有的,不过首先我们可能要针对我们文件本身做一下简单的改造

webpack-merge

webpack-merge 是一个简单的工具,可以帮助我们快速的合并两个两个webpack的配置项,但是实际上我理解这个工具他其实有点类似于Object.assign的递归版本,当然其中的一些特殊的键值处理可能也是不太一样的,比如针对plugins他的操作可能就更加类似plugins: [...a.plugins, ...b.plugins],那么我们为什么要是用这个工具呢?

我想其实比较好理解的,我们针对不同环境可能会需要的webpack配置可能是不一致的,并不是每一个环境都要做到说babel的编译啊,打包报告的输出啊,或者其他的等等,所以我们针对不同环境的配置可能会不同。但是又不是完全的不同,可能会有一部分的配置是想通的,那么这个是有就需要webpack-merge来协助我们针对配置进行整合。首先安装一下依赖npm install webpack-merge --save,然后分离出一下的几个文件目录。(下面我已经把整个完整的这一篇的代码给加入进去了,前后的差异会在后面的篇幅中介绍,遇到不懂得请各位老师先不要着急)

  • webpack.base.js
// webpack.base.js
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

const kb = 1024
const mb = 1000 * kb

module.exports = {
  entry: path.resolve(__dirname, './src/main.js'),
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  module: {
    rules: [{
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: "usage", // 用到才引入
                  corejs: 3 // 指定版本号
                }
              ]
            ],
            plugins: [
              [
                "@babel/plugin-transform-runtime",
                {
                  corejs: 3 // 指定版本号
                }
              ]
            ]
          }
        }
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'vue-style-loader',
          use: [{
              loader: 'css-loader',
              options: {
                esModule: false
              }
            },
            'sass-loader'
          ]
        })
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/i,
        use: [{
          loader: 'url-loader',
          options: {
            limit: 10 * kb,
            name: 'images/[name].[hash:7].[ext]',
            publicPath: './',
            esModule: false
          },
        }, ],
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, './index.html')
    }),
    new ExtractTextPlugin('styles.css')
  ]
}
  • webpack.dev.js
// webpack.dev.js
const path = require('path');
const baseConfig = require('./webpack.base.js')
const { merge } = require('webpack-merge');
const webpack  = require('webpack')


const devObj = {
  mode: 'development',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist'),
    publicPath: '/'
  },
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    clientLogLevel: 'info',
    host: "0.0.0.0",
    port: 8080,
    hot: true,
    open: true,
    compress: true,
    contentBase: false,
    publicPath: '/'
  },
  plugins: [
    // 保证生成的资源数据一定是正确的
    new webpack.NoEmitOnErrorsPlugin(),
    // 生成source map 代替 devtools
    new webpack.SourceMapDevToolPlugin()
  ]
}


module.exports = merge([baseConfig, devObj])
  • webpack.prod.js
// webpack.prod.js
const path = require('path');
const {
  CleanWebpackPlugin
} = require('clean-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const baseConfig = require('./webpack.base.js')
const { merge } = require('webpack-merge')

const prodObj = {
  mode: 'production',
  // 文件的出口也就是我们要生成的文件
  output: {
    // 这里[name]就是entry是什么名字的就是啥名字啦
    filename: '[name].js',
    path: path.resolve(__dirname, './dist'),
    publicPath: './'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new BundleAnalyzerPlugin()
  ]
}


module.exports = merge([baseConfig, prodObj])
  • webpack.serve.js
const path = require('path');
const devObj = require('./webpack.dev.js')
const { merge } = require('webpack-merge');
const webpack  = require('webpack')


const serveObj = {
  entry: ['webpack-hot-middleware/client.js', path.resolve(__dirname, './src/main.js')],
  plugins: [
    // 热替换
    new webpack.HotModuleReplacementPlugin()
  ]
}

module.exports = merge([devObj, serveObj])

以上是本篇文章完整的代码,并不是说这样写就完美了,只是这样可以满足一个比较基本的配置项,并且有利于大家进行学习,后续的优化篇的我也会跟大家一起根据我的一些思路进行优化。首先我们先来针对上面的几个文件进行一个大体的总结。 可能之前有看build篇的朋友就会知道,其实这个几个文件里面,有一些内容上的变化的可能就是webpack.dev.jswebpack.serve.js 所以我们接下来的文章内容会针对这两个文件展开叙述!

webpack.dev.js

devtool

中文官方文档其实有解释说这个字段,是做什么的,不过我还是要在这里废话一下。这个字段主要是用来配置调试过程中所需要的source map类型。可能有一些小伙伴不太清楚source map是啥?那么简单的来说呢就是由于我们打包后的代码是一行以及混淆的。如果我们在调试的时候这样子肯定是不方便的,所以我们需要source map文件来帮我们记录原本对应的一些代码行的位置,并且可以方便我们在浏览器端的debug操作。当更加详细的内容可以通过阮一峰老师JavaScript Source Map 详解来进行学习。

那么针对webpack官方呢,实际上给我们有很多的一些不同的配置选择,如下图 devtool表格

是不是看的头都大了。哈哈哈哈哈哈但是其实,他是有一些规律可循的~ 我在掘金上也看到有一个大佬的文章是写的比较清晰的这里也分享给大家**点击这里** 自行观看,点赞哈~ 那么基于大佬的这一篇文章嘛~ 我这边选择cheap-module-eval-source-map作为我们的一个devtool的参数配置,具体还是为了我们在开发的过程中,针对错误,debug可以更加准确的定位,以及更加方便的去使用~

devServer

devServer 实际上是 webpack-dev-server 的简称。主要就是为了方便在开发环境下,为开发者提供一个测试服务器,那么这个配置项实际上有很多的内容,这里我选取一些比较基础的字段进行介绍~

host

启动本地服务的ip地址,大部分情况我们填写0.0.0.0就可以。因为我们填写0.0.0.0的时候呢~ 既可以通过127.0.0.1 进行访问,也可以通过内网地址192.168.1.XXX进行访问。而写成两者中的其中一种却没有办法达到这个效果。

port

本地服务的端口,一般就选常见的8080就可以,当然也可以根据你的需要进行修改

hot

启用 webpack 的模块热替换特性,这个属性主要是方便于在本地开发的情况下,开发可以不用通过刷新页面,来看到最新的代码情况,一般我们在开发环境设置为true就可以了

open

是否在启动服务的同时,在浏览器打开对应的服务地址host:port,开发环境设置为true

compress

是否启动gzip,这个主要是本地服务针对是否针对资源进行gzip进行压缩,提高本地的资源的请求速度,本地开发设置为true,关于gzip本身可能也会有一部分的小伙伴不太了解是啥,简单的说来就是一种服务器端跟浏览器端都支持的编码格式,更加有利于压缩文件体积,在很多很生产环境下,我们也会通过gzip去减小静态文件的一个体积,减少传输时间

clientLogLevel

主要是针对一些webpack内的日志打印类型的一个设置,可以设置成不同的类型来方便自己作区分,代码简单如下:

var INFO = 'info';
var WARN = 'warn';
var ERROR = 'error';
var DEBUG = 'debug';
var TRACE = 'trace';
var SILENT = 'silent'; // deprecated
// TODO: remove these at major released
// https://github.com/webpack/webpack-dev-server/pull/1825

var WARNING = 'warning';
var NONE = 'none'; // Set the default log level

log.setDefaultLevel(INFO);

function setLogLevel(level) {
  switch (level) {
    case INFO:
    case WARN:
    case ERROR:
    case DEBUG:
    case TRACE:
      log.setLevel(level);
      break;
    // deprecated

    case WARNING:
      // loglevel's warning name is different from webpack's
      log.setLevel('warn');
      break;
    // deprecated

    case NONE:
    case SILENT:
      log.disableAll();
      break;

    default:
      log.error("[WDS] Unknown clientLogLevel '".concat(level, "'"));
  }
}
proxy

配置反向代理,方便解决跨域问题(这个功能灰常有用!),具体配置点击查看

那么上面也就针对devServer的一些配置做了简单的介绍,实际上devServer所提供的能力非常强大,我们这边只做了一些基础的介绍,感兴趣的小伙伴可以自己深入

webpack.NoEmitOnErrorsPlugin

其实这个插件的名称,已经非常的直白,不过如果没有了解过如何自定义 webpack 插件的小伙伴肯定会一脸懵逼,实际上是这样的emit实际上是作为webpack compiler的一个钩子暴露出来 webpack emit 截图

那么我们在参照一下这个的插件的名称就很清晰,当编译的过程中发生,或者在 webpack运行的其他阶段发生错误时,不会吧对应的资源生成到目录。

那么这个插件主要的用处就是避免生成一些错误资源~ 也是一个比较有实际作用的小插件

运行

然后配置好这些选项后呢,我们可以打开我们的package.json里面,添加如下指令

 "scripts": {
	...
    // 新增下面这一行
    "dev": "webpack-dev-server --inline --progress --config webpack.dev.js",
	...
  },

然后在命令行中运行npm run dev这个时候我们就能在http://0.0.0.0:8080打开我们本地的静态资源啦~

webpack.serve.js

那么我们都在dev里面配置了那么多本地服务相关的东西,这个webpack.serve.js又是拿来做什么的呢?这个时候我们就要了解一下webpack-dev-middlewarewebpack-hot-middleware

webpack-dev-middleware

webpack-dev-middleware 实际去支持了webpack的本地调试服务的这么一个express的middleware~什么意思呢?就是说我们之前是通过webpack-dev-server来进行的这么一个本地服务的启动,但是由于他是内置的,即使是在再多的可配置项也会存在一定的限制因素,比如你想同时启动多个服务,或者 你想在node的代码里面一些骚东西,那都是比较麻烦的~ 但是如果通过webpack-dev-middleware来启动本地服务的话,相对的灵活性会更高。除去webpack本身,可能你想要加一些本地的mock服务器,或者一些更加灵活的规则都是可行的,那么我们这边要如何使用呢~

根据github上的叙述,我们只要在跟目录创建xxx.js并加入一下代码,即可通过node xxx.js使用

const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
const compiler = webpack({
  // webpack options
});
const express = require('express');
const app = express();

app.use(
  middleware(compiler, {
    // webpack-dev-middleware options
  })
);

app.listen(3000, () => console.log('Example app listening on port 3000!'));

ok 我们了解完webpack-dev-middleware之后不要着急,我们继续来看看webpack-hot-middleware是做什么的~

webpack-hot-middleware

实际上我们根据webpack-hot-middleware github的情况,可以得知~ 这个就是我们在devServer中的hot。但是这里的配置项,可能会更多一些~

server.js

那么我们既然已经知道了这两个middleware的作用了,就赶紧来根据我们情况来编写我们对应的一个server.js,首先先安装npm install express webpack-dev-middleware webpack-hot-middleware --save-dev

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.serve.js');
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler, {
  publicPath: '/'
}));

app.use(require("webpack-hot-middleware")(compiler));

app.listen(3000, function () {
  console.log('Example app listening on port 3000!\n');
});

接着在 package.json里面,添加如下指令

 "scripts": {
	...
    // 新增下面这一行
	"start": "node server.js",
	...
  },

运行npm run start就可以在localhost:3000打开它了

现存问题

那么到这里,基本上就可以满足本地开发的大部分需求了,不过如果有更多一些开发需求 或者 优化,不如我们我们继续往下看~

通用配置

webpack的很多通用配置项,实际上都散乱在好几个配置文件中,如果全部变成一个Obj又会显得特别冗余,所以这个地方我们可能要想办法抽取出来一个公用的常用配置项,来方便我们正常的配置需求

性能

在我们build篇中更多的情况下,只考虑到了一些基本的实现,但是对于打包后的文件分包,打包时常几乎都还没有考虑~

开发体验

目前模块间的模块引入,还是要importrequire 进行引入,那么我们有没有办法根据文件目录或者是区分出一些公共模块进行自动导入,包括一些全局性的参数,方法等

那么以上提到的这些就是我暂时能想到的一些需要共同去优化的点,我会放在《webpack-vue脚手架之优化篇》中跟大家一起去解决

结尾

这一篇给大家带来的是一篇最基础的这么一个dev的配置,可能对于大部分的同学来说是仅仅不够的,那么我会把我这边所知道的一些比较有用的一些技巧,配置,插件,loader放到下一期的优化篇 进行一个统一的讲解介绍(大长篇),感兴趣的朋友记得点赞,留言,关注我哦~