为什么搭建本地服务器?
之前写代码,每次更改都要npm run build
编译相关代码很麻烦。
我们希望做到,当文件发生变化时,可以自动完成编译和展示。(备注:live-server插件可以自动编译展示)
自动完成编译,webpack提供以下方式
webpack watch mode
webpack-dev-serve(常用)
webpack-dev-middleware
Webpack watch
package.json中配置
"scripts": {
"build": "webpack --watch"
}
在webpack.config.js中配置
watch:true
webpack-dev-serve
上面两种方式的配置都可以监听文件变化,但本身没有自动刷新浏览器功能,开发中不常使用
目前,我们可以在VSCode中通过live serve插件完成浏览器自动挡刷新功能。
如果我们希望在不使用live-serve时,仍具备live reloading(实时重加载),安装webpack-dev-serve
1.安装webpack-dev-serve
npm install webpack-dev-serve -D
package.json
"scripts": {
"build": "webpack",
"serve": "webpack serve"
}
npm run serve
就可以实时重载浏览器页面了。
注意:
serve通过webpack-cli来帮助我们做解析的
webpack-dev-serve在编译之后不会写入到任何输出文件,而是将bundle文件保留在内存中。webpack-dev-serve使用了一个memfs库。
2.配置文件
开发阶段:contentBase
打包阶段:CopyWebpackPlugin
我们在开发阶段,如果每次都对资源进行copy,比如资源有很多视频,这样很耗费性能。
所以,开发阶段,我们常采用contentBase来告诉devServer从什么位置找文件。
如果资源没有从webpack加载到,会从contentBase配置的路径进行加载。
等到打包阶段时,使用CopyWebpackPlugin
devServer: {
contentBase: "./public"
}
模块热替换
什么是HMR?
Hot Module Replacement模块热替换
指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面
HMR通过以下几种方式来提高开发速度
不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
只更新需要变化的内容,节省开发时间
修改了css、js源码,会立即在浏览器更新
如何使用HMR?
默认情况下,webpack-dev-serve已支持HMR,我们只需开启即可。
在不开启HMR的情况下,当我们修改了源码后,整个页面会重新刷新,使用的是live reloading
devServe:{
hot:true
}
框架的HMR
在开发Vue、React项目,我们修改了组件,希望进行热更新,应该如何操作呢?
vue开发中,我们使用vue-loader
react开发中,有react-refresh
HMR原理
我们先要明白http连接和socket连接有什么区别。
TCP、HTTP、Socket
Socket常用在即时通信,比如微信、直播聊天、送礼物、直播进场等场景
http连接
客户端发送http请求—>和服务器建立连接—>服务器做出响应—>断开连接
HTTP是短连接,Socket(基于TCP协议的)是长连接。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。
HTTP连接服务端无法主动发消息,Socket连接双方请求的发送先后限制。这点就比较重要了,因为它将决定二者分别适合应用在什么场景下。HTTP采用“请求-响应”机制,在客户端还没发送消息给服务端前,服务端无法推送消息给客户端。必须满足客户端发送消息在前,服务端回复在后。Socket连接双方类似peer2peer的关系,一方随时可以向另一方喊话。
HMR的原理是什么呢?如何做到只更新一个模块中的内容呢?
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket)
express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
HMR Socket Server是一个socket长连接。
长连接有个好处是建立连接后双方可以通信(服务器可以直接发送文件到服务端)。
当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js(update chunk)。
通过长连接,可以直接将这两个文件主动发送给客户端(浏览器)。
浏览器拿到这两个新文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新。
host配置
host设置主机地址:默认localhost(localhost本质是域名,被解析成127.0.0.1)。
如果希望其他地方也能访问,设置为0.0.0.0
127.0.0.1
回环地址:主机发出去的包,直接被自己接收。
正常的数据库包 应用层—传输层—网络层—数据链路层—物理层。而回环地址,是在网络层直接就被获取到了,是不会经过数据链路层和物理层的。比如我们监听127.0.0.1时,在同一网段下的主机中,通过ip地址是不能访问的。
0.0.0.0,在同一个网段下的主机中,通过ip地址是可以访问的
port 设置监听端口,默认8080
open是否打开浏览器:默认false不打开
compress是否为静态文件开启gzip 压缩:默认false
解决跨域:
1.当前静态资源和api服务器部署到一块(Tomact、Express、Koa ),这样,访问不会有跨域问题
2.让服务器把跨域关掉,但不安全
3.nginx代理
解决跨域都需要前端和后端配合,开发阶段一般配置devServe里的代理(proxy在开发阶段配置),部署阶段后端配置
proxy官网教程
Proxy
proxy目的是设置代理来解决跨域访问的问题
比如我们一个api请求是http://localhost:8888,但本地启动服务器的域名是http://localhost:8000,这个时候发送网络请求就会出现跨域的问题
那么我们可以将请求先发送一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了。
进行如下设置:
target:代理到的目标地址。
比如/api-hy/moment会被代理到http://localhost:8888/api-hy/moment
pathRewrite:默认情况下,我们的/api-hy也会被写入到url中,如果希望删除,可以使用pathRewrite
secure:默认true,默认情况下不接收转发到https的服务器上
changeOrigin:表示是否更新代理后请求的headers和host地址
resolve模块解析
resolve用于设置模块如何被解析
可以帮助webpack从每个require/import语句中,找到需要引入的合适的代码模块
webpack能解析三种文件路径
绝对路径
由于已经获得文件的绝对路径,因此不需再进一步解析
相对路径
在这种情况下,使用import或require的资源文件所处的目录,被认为是上下文目录
在import/require中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径
模块路径
在resolve.modules中指定的所有目录检索模块
默认值['node_modules'],所以默认会从node_modules中查找文件
如何确定我们导入的模块是文件还是文件夹(库)呢?
如果文件有扩展名,则直接打包文件。
否则,将使用resolve.extensions选项作为文件扩展名解析
resolve: {
extensions: [".js", ".json", ".mjs", ".vue", ".ts", ".jsx", ".tsx"]
}
如果是文件夹
根据resolve.mainFiles配置选项中指定的文件顺序进行查找
resolve.mainFiles的默认值是[‘index’],再根据resolve.extensions来解析扩展名
extensions和alias配置
extensions:解析到文件时自动加扩展名
alias:给常见路径起别名
开发和生成环境配置
当我们打包的时候,如果还是使用webpack.config.js配置文件,不是很合理。配置中有些在开发中,有些在生成中使用。
所以,要讲开发环境和生成环境配置分离。
1.我们创建3个文件
webpack.comm.config.js:共同配置
webpack.dev.config.js:开发阶段配置
webpack.prod.config.js:生成阶段配置
2.将comm合并到dev、prod中。
npm install webpack-merge -D
通过merge函数将comm配置分别合并到dev、prod中
"scripts": {
"build": "webpack --config ./config/webpack.prod.config.js",
"serve": "webpack serve --config ./config/webpack.dev.config.js"
}
这样,我们在执行npm run build
打包生成阶段。 npm run serve
打包开发阶段
webpack.comm.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { DefinePlugin } = require("webpack");
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
target: "web",
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "../build"),
filename: "js/bundle.js",
},
resolve: {
extensions: [".js", ".json", ".mjs", ".vue", ".ts", ".jsx", ".tsx"],
alias: {
"@": path.resolve(__dirname, "../src"),
"js": path.resolve(__dirname, "../src/js")
}
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
// },
{
test: /\.(jpe?g|png|gif|svg)$/,
type: "asset",
generator: {
filename: "img/[name]_[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /\.(eot|ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "font/[name]_[hash:6][ext]",
},
},
{
test: /\.js$/,
loader: "babel-loader"
},
{
test: /\.vue$/,
loader: "vue-loader"
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
title: "哈哈哈哈"
}),
new DefinePlugin({
BASE_URL: "'./'",
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
}),
new VueLoaderPlugin()
],
};
webpack.dev.config.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.comm.config');
module.exports = merge(commonConfig, {
mode: "development",
devtool: "source-map",
devServer: {
contentBase: "./public",
hot: true,
// host: "0.0.0.0",
port: 7777,
open: true,
// compress: true,
proxy: {
"/api": {
target: "http://localhost:8888",
pathRewrite: {
"^/api": ""
},
secure: false,
changeOrigin: true
}
}
},
})
webpack.prod.config.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const {merge} = require('webpack-merge');
const commonConfig = require('./webpack.comm.config');
module.exports = merge(commonConfig, {
mode: "production",
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: "./public",
globOptions: {
ignore: [
"**/index.html"
]
}
}
]
}),
]
})