概述

以目前的状态去应对日常的开发工作还远远不够,那是因为编写源代码,再通过webpack打包,然后运行应用,最后刷新浏览器这周周而复始的方式过于原始。

如果说我们在实际的开发过程中还按照这种方式去使用必然会大大降低我们的开发效率。那究竟该如何去提高我们的开发效率呢,那这里呢我们先对我们理想的开发环境做出设想。

首先我们希望这样一个开发环境他必须能够使用http的服务去运行而不是以文件的形式去预览,那这样我们一来可以更加接近生产环境的状态,二来呢我们可能需要去使用ajax之类的一些api,那这些api呢,文件的形式去访问他是不被支持的。

其次我们希望这样一个环境中,去修改源代码过后,webpack就可以自动帮我们完成构建,然后我们的浏览器可以及时的显示最新的结果,那这样的话就可以大大的减少我们在开发过程中额外的那些重复操作。

最后我们还需要这样一个环境它能够去提供sourceMap支持,那这样依赖的话我们运行过程中一旦出现了错误就可以根据错误的堆栈信息快速定位到源代码当中的位置,便于我们调试应用。

那对于以上这些需求呢webpack都已经有了相应的功能去实现了,那接下来我们一起去重点了解,具体如何去增强我们使用webpack的开发体验。

自动编译

用命令行手动重复去使用webpack命令从而去得到最新的打包结果,这种办法特别的麻烦,我们也可以使用webpack-cli提供的一种,watch的工作模式去解决这个问题,如果说你之前了解过一些其他的构建工具。

那你应该对这种模式并不陌生,那在这种模式下面,我们项目下的源文件会被监视, 一旦这些文件发生变化,那他就会自动重新去运行我们的打包任务。

具体的用法也非常简单,就是我们再去启动webpack命令时我们添加一个--watch的参数,那这样的话webpack就会以监视的模式去运行。

yarn webpack --watch

那在打包完成过后呢,我们的cli不会立即退出,他会等待文件文件的变化,然后再次的工作一直到我们手动结束这个cli。

那在这种模式下面我们就只需要专注编码,不比再去手动的完成这些工作了。那这里我们可以在在开启一个新的命令行终端,同时以http的形式去运行我们的应用。

http-server ./dist

然后我们打开浏览器去预览我们的应用。

我们可以将浏览器移至我们屏幕的右侧,然后将编辑器移至左侧,那此时我们就可以尝试修改源代码,保存过后我们以观察模式工作的webpack就会自动重新打包,打包完成过后我们就可以到浏览器中取刷新页面,然后去查看最新的页面结果。

自动刷新浏览器

通过watch模式启动webpack,当文件内容发生改变时,webpack就会自动编译,然后我们回到浏览器,手动刷新后就可以看到运行结果。

那如果说流浏览器能在编译过后自动去刷新,那我们的开发体验呢将会更好一些,那如果之前你已经了解过一个叫做,browser-sync的工具,那你就会知道这个工具呢就会帮我们实现这个自动刷新的功能。

yarn add --global browser-sync

那这里呢,我已经在全局范围安装了这个工具,我们这里先结束掉http-server启动的这个http服务,然后我们使用browser-sync去启动http服务。这里同时我们还要监听dist文件下的文件变化。

browser-sync dist --files "**/*"

那启动过后呢我们再回到编辑器当中,然后我们尝试修改源文件。那保存过后我们发现浏览器自动刷新然后去显示最新的结果。

那他的原理就是webpack自动打包我们的源代码到dist当中,那dist的文件变化又被browser-sync监听了,从而去实现了我们的自动编译并且自动刷新浏览器。

不过这种方式去解决有很多弊端,第一就是我们操作上太麻烦了,因为我们需要同时使用两个工具,第二就是我们再开发效率上会有一些降低,因为这个过程中webpack会不断将文件写入磁盘,然后browser-sync再把它从磁盘中读出来,那这个过程中,一次就会多出两步的磁盘读写操作。

所以说我们还需要继续去改善这个开发体验。

WebpackDevServer

那根据他的名字我们就应该知道,他提供了一个开发服务器,并且他将自动编译和自动刷新浏览器等一系列对开发非常友好的功能呢都全部集成在了一起。

那我们这呢可以使用这个工具直接去解决我们之前的问题。那因为这是一个高度集成的工具,所以说他使用起来也非常的简单,我们这里打开命令行,然后我们通过yarn将webpack-dev-server作为我们项目的开发依赖去安装。

yarn add webpack-dev-server --dev

安装完成过后这个模块为我们提供了一个webpack-dev-server的cli程序。我们同样可以用过yarn去运行这个cli。或者可以把它定义到npm scripts中。

yarn webpack-dev-server

我们去运行这个命令,那他内部就会自动去使用webpack去打包我们的应用。并且会启动一个http-server去运行我们的打包结果。

在运行过后他还会监听我们的代码变化,一旦我们的源文件发生变化,他就会自动立即重新打包。那这一点和我们的watch模式是一样的。

不过这里我们也需要注意,webpack-dev-server为了提高工作效率他并没有将打包结果写入到磁盘中,根据我们左侧文件数我们可以发现并没有出现dist目录。他是将打包结果暂时存放在内存当中,而内部的http-server也就是从内存当中把这些文件读出来,然后发送给浏览器。那这样一来他就会减少很多磁盘不必要的读写操作,从而大大提高我们的构建效率。

这里我们还可以为这个命令传入一个--open的参数,用于自动唤起我们的浏览器,去打开我们的运行地址。

yarn webpack-dev-server --open

那打开浏览器过后呢,此时如果说你有两块屏幕的话就可以把浏览器放到另外一块屏幕当中,然后我们可以实现这种一边编码,一边及时预览的开发环境。

静态资源访问

那此时我们的开发体验就是我们修改完文件会自动打包输出到浏览器,那也就是只要通过webpack打包能够输出的文件都可以正常被访问到。

但是如果你还有一些静态文件也需要作为开发服务器的资源被访问的话,那你就需要额外的去告诉webpack-dev-server。

具体的方法就是在我们webpack的配置文件当中去添加一个对应的配置,我们回到配置文件当中。

那这里我们在配置对象当中去添加一个dev-server的属性,那这个属性书专门用来为webpack-dev-server去指定相关的配置选项。

那我们可以通过这个配置对象的contentBase属性来去指定额外的静态资源路径。那这个属性可以是一个字符串或者是一个数组, 也就是说我们可以配置一个或者是多个路径。我们这里将这个路径设置为项目根目录中的public目录。

可能有人会有疑问,因为之前我们已经通过插件将这个目录输出到了我们输出目录。那按照刚刚的说法,我们所有输出的文件都可以直接被server也就是直接可以在浏览器端访问到。那按道理来讲这里这些文件就不需要再作为开发服务器的额外的资源路径了。

事实情况确实如此,如果你能这么想那也证明你确实理解了这样一个点,但是呢,我们在实际去使用webpack的时候我们一般都会把copy-webpack-plugin这样的插件留在上线前的那次打包中使用。那在平时的开发过程中我们一般不会去使用它,那这是因为在开发过程中我们会频重复执行打包任务。那假设我们需要copy的文件比较多,或者是比较大,那如果说我们每次都去执行这个插件的话,那我们打包过程中的开销就会比较大那速度自然也就会降低了。

那由于这是额外的话题,具体的操作方式具体怎么样让我们在开发阶段不去使用copy-webpack-plugin然后在上线前那一刻我们再去使用这种插件,这种操作方式我们在后续再来介绍。这里我们先注释掉copy-webpack-plugin。

这样确保我们在打包过程中不会再去输出public目录中的静态资源文件。

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    devServer: {
        contentBase: './public',
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'file-loader'
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Webpack Plugin Sample',
            meta: {
                viewport: 'width=device-width'
            },
            template: './src/index.html'
        }),
        new HtmlWebpackPlugin({
            filename: 'about.html'
        }),
        // new CopyWebpackPlugin(['public'])
    ]
}

然后我们回到命令行,再次执行webpack-dev-server 那启动过后我们此次public目录当中并没有被copy到我们输出目录。

那如果说我们webpack只去加载那些我们打包生成的文件的话,那我们publc目录中的文件应该是访问不到的,但是我们通过contentBase已经将他指定为了额外的资源路径,所以说我们是可以访问到的,我们打开浏览器。

这里我们去访问的页面文件,包括我们再来访问的bundle.js文件都是来源于打包结果当中。

然后我们再去尝试一下访问favicon.ico, 那这个文件就是来源于我们contentBase中所配置的public目录了,除此之外呢,像这个other.html他也是这个目录当中所指定的文件。

以上就是contentBase他可以用来去为webpack-dev-server额外去指定一个静态资源目录的操作方式。

代理

webpack-dev-server在启动服务时创建的是一个本地服务,访问地址一般为localhost + 端口号,而最终上线过后呢我们的应用一般又和我们的API会部署到同源地址下面。

那这样的话就会有一个非常常见的问题,那就是我们在实际生产环境当中我们可以直接去访问API, 但是回到我们开发环境当中就会产生跨域请求问题。

那可能有人会说我们可以使用跨域资源共享的方式去解决这个问题,事实也确实如此,我们如果请求的这个API, 支持CORS的话那我们这个问题就不成立了。

但是并不是每种情况下我们的服务端的API都一定要支持CORS的,如果说我们前后端同源部署的话也就是我们的域名,协议和端口是一致的话,那这种情况下我们根本没有必要去开启CORS。

所以以上这个问题还是经常会出现。那解决这个问题最好的办法就是在我们开发服务器去配置代理服务也就是把我们的接口服务代理到本地的开发服务地址。

webpack-dev-server就支持直接通过配置的方式去添加代理服务,具体的用法我们一起来尝试一下。

我们这里的目标就是将github的api代理到我们本地的开发服务器当中。我们先在浏览器尝试访问一下其中的一个接口。

github的接口的Endpoint一般都是在根目录下,例如我们这里所使用的user

https://api.github.com/users

Endpoint可以理解为接口端点,入口。

知道了接口的地址之后我们回到配置文件当中,这里我们在devServer当中去添加一个proxy属性。

那这个属性就是专门用来添加代理服务配置的,这个属性是个对象,其中每一个属性就是一个代理规则的配置。属性的名称就是我们需要被代理的请求路径前缀,也就是我们请求以哪一个地址开始,就会走代理请求,一般为了辨别,我都会将其设置为/api, 也就是我们请求我们开发服务器中的/api开头的这种地址,我们都会让他代理到我们的接口当中。

那他的值是为这个前缀所匹配到的代理规则配置。我们将代理目标设置为https://api.github.com, 也就是说当我们请求/那我们的代理目标就是api.github.com这样一个地址。

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    devServer: {
        contentBase: './public',
        proxy: {
            '/api': {
                target: 'https://api.github.com'
            }
        }
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'file-loader'
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Webpack Plugin Sample',
            meta: {
                viewport: 'width=device-width'
            },
            template: './src/index.html'
        }),
        new HtmlWebpackPlugin({
            filename: 'about.html'
        }),
        // new CopyWebpackPlugin(['public'])
    ]
}

那此时如果我们去请求http://localhost:8080就是webpack-dev-server的这个地址。然后加上/api/users我们就相当于请求了https://api.github.com/api/users。意思是我们请求的路径是什么,他最终代理的这个地址,路径是会完全一致的。

而我们需要请求的这个接口地址呢,实际上在api.github.com/users 并没有 /api/users, 所以我们对于代理路径中的/api我们需要重写的方式把他去掉。

我们可以添加一个pathRewrite属性,来去实现代理路径的重写。那重写规则就是把我们路径中以/api开头的这段字符串替换为空字符串,因为pathRewrite这个属性最终会以正则的方式去替换我们请求的路径。所以说我们这里已^表示开头。

除此之外我们还需要设置changeOrigin属性为true,这是因为默认我们代理服务器会以我们实际在浏览器当中请求的主机名,就是localhost:8080作为代理请求的主机名。

也就是我们在浏览器端对我们代理过后这个接口发起请求,那这个请求背后肯定还需要请求到github的那个服务器,那请求的过程中会带一个主机名,那这个主机名默认情况下使用的是我们用户在浏览器端发起请求的这个主机名也就是localhost:8080。

而一般情况下,服务器那头是要根据主机名去判断我这台,因为你一个请求请求到服务器过后,服务器一般会有多个网站,那他会根据主机名去判断,这个请求是属于哪个网站从而把这个请求指派到对应的网站。

那localhost:8080对于github的服务器来说肯定是不认识的,所以我们这里需要去修改。

那changeOrigin=true的这种情况下就会以实际我们代理请求这次发生的过程中的主机名去请求。那我们请求github的这个地址,我们真正请求的应该是api.github.com这样一个地址,所以说他的主机名肯定就会保持原有状态。

那这个时候呢,我们就不用在关心我们最终把他代理成了什么样。我们只需要正常的去请求就可以了。

{
    contentBase: './public',
    proxy: {
        '/api': {
            target: 'https://api.github.com'.
            pathRewrite: {
                '^/api': ''
            },
            changeOrigin: true
        }
    }
}

那完成以后我们再回到命令行终端,然后去运行webpack-dev-server, 然后我们再去打开浏览器。

yarn webpack-dev-server

那这里我们直接去尝试请求localhost:8080/api/users

localhost:8080/api/users

请求完成过后我们看到,此时这个地址就被代理到了我们github的用户数据接口。

那我们这么地方可以再次回到代码当中来去使用这个代理过后的地址去请求接口。这种地址就不用再去担心跨域问题,因为他是同源地址。

可能在这个过程当中针对于changeOrigin 也就是那个主机名那块可能会有一些只是了解了前端基础的同学会不会特别清楚。这个原因是因为我们在http里面有一些相关的知识点。可能之前没有了解过,可以再去查一下就是host也就是主机名相关的一些概念,就可以解决这些问题了。