webpack
1. package.json命令启动配置环境变量
cross-env是一个用于跨平台设置和使用环境变量的脚本工具, cross-env NODE_ENV=online
就是设定NODE_ENV的值是"online".
在webpack.config.js里可以通过process.env.NODE_ENV
获取
{
"dev": "cross-env NODE_ENV=online node build/dev-server.js",
}
2. node-sass安装问题处理
node-sass由于和node版本关联太深, 很容易下载不下来, 不建议继续使用而是使用sass.
- package.json里去掉node-sass
- "/deep/" 全局替换成 "::v-deep"
3. webpack的配置文件的本地调试.
- 在配置webpack.config.js时, 我们需要测试配置文件是否正确, 看导入库是否正常, 变量值是否正确, 就需要进行断点调试.
原来一直是输出log, 但是那样不是很直观, 不好看代码是怎么走流程的. 而断点调试可以. - 点击运行脚本右边的设置按钮,出现 launch.json. 这是启动配置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\vue2-elm-master\\build\\build.js"
},
{
"type": "node-terminal", // 环境 node
"name": "运行脚本: vue2-elm-build", // 名称
"request": "launch",
"command": "npm run build", // 运行的终端命令
"cwd": "${workspaceFolder}\\vue2-elm-master" , //根目录地址.
}
]
}
4. 本地开发开启服务器的原因
- 解决线上部署后的资源路径问题: https://www.cnblogs.com/zhuzhenwei918/p/6866094.html
- 解决history模式下的URL fallback问题: https://router.vuejs.org/zh/guide/essentials/history-mode.html
5. express的中间件.
通过浏览器, 地址: localhost:3000 可以看到返回值.
- app.get("/", ()=>{}), 会处理 localhost:3000/ 的请求
- app.use是中间件, 可以在app.get前/后进行响应处理. 比如修改地址, 添加记录,等.
- app.use(()=>{}), 的第一个参数是函数, 那就是全局中间件, 监控所有的请求.
- app.use(path, ()=>{}), 的第一个参数是字符串路径, 那就是路由中间件, 监控特定的请求.
- app.use((err, req, res, next)=>{}), 的函数参数, 如果有四个参数, 那么第一个参数就会变成error,可以监控报错信息.
关于异常中间件
- 异常中间件全局只会生效一个, 就是第一个, 经过了第一个异常中间件后, 报错就被处理了, 第二个异常中间件就接受不到了.
- 异常中间件可以传递给普通中间件. 异常中间件的
next()
执行后, 回到下一个普通的中间件. - 异常中间件需要放到所有中间件的最后
- 异常中间件只能捕获回调函数中的异常.也就是
get
,post
这些监控函数的异常. 在全局的异常,和get
里的promise里的异常, 异常中间件获取不到.
const express= require("express");
// 创建
const app = express();
const port = 3000;
// 前置中间件
app.use((req, res, next)=>{
console.log("before middle ware");
next();
})
// 监控. 处理 localhost:3000的请求.
app.get("/", (req, res, next)=>{
console.log("get");
res.send("<div style='color: red'>111</div")
next();
})
// 路由中间件. 处理 localhost:3000/test 的中间件
app.use("/test", (req, res, next)=>{
console.log("test middle ware");
res.send("test");
next();
})
// 路由中间件 处理 localhost:3000/error 的中间件
app.use("/error", (req, res, next)=>{
console.log("error middle ware");
throw(new Error("报错"))
next();
})
// 全局错误处理中间件, 有报错, 比如console.error, 就会来到这里.
app.use((error, req, res, next) =>{
console.log("error middle ware", error.message);
next();
})
// 后置中间件
app.use((req, res, next) =>{
console.log("after middle ware");
})
// 启动
app.listen(port, ()=>{
console.log("express启动", port);
})
6. express的报错异常的捕获.
- 在app.get/post里的回调的异常可以用app.use((err, req, res, next))捕获.
- 全局异常捕获用
process.on("uncaughtException",()=>{})
- app.get/post的promise里的异常用
proces.on("unhandledRejection",()=>{})
; - process是node的进程对象, 控制进程的各个生命周期, 事件监控.
// 全局异常捕获.
process.on("uncaughtException", (error)=>{
console.log("uncaughtException", error);
})
// 捕获promise中的报错.
process.on("unhandledRejection", (err)=>{
console.log("unhandledRejection", err);
})
7. 设置服务器的静态文件.
服务器的路径一般都是走的路由监控请求. 但是有的文件是需要纯粹查看的文件, 比如在浏览器直接查看图片, pdf文件, txt文件, 视频文件, 音频文件等.
这就是服务器的静态文件, 指能直接查看静态文件的路径.
// 根目录下单 "./static"文件夹中的文件就可以通过 localhost:8080/static/xxx来直接查看了.
// 比如 "./static/a.pdf"的地址就是 localhost:8080/static/a.pdf .
app.use("/static", express.static("static"))
8. https协议的服务器.
配置https服务,需要购买https证书, 放到根目录的https
下, xxx.key是私钥, xxx.pem是公钥.
我没买, 暂且先这样.
const https = require('https');
const httpsPort = 443;
const options = {
key: fs.readFileSync("./https/xyz.key"), //私钥
cert: fs.readFileSync("./https/xyz.pem"), // 公钥
};
const httpsServer = https.createServer(options, app);
httpsServer.listen(httpsPort, ()=>{
console.log("https 服务启动成功", httpsServer);
})
vue-cli的实现
vue2-elm-master\build\dev-server.js 文件做的就是vue-cli做的内容. 可以看看,我写了注释.
9. webpackdevMiddleware webpack的开发服务中间件
- webpack-dev-server是webpack的开发模式的服务器, 启动后, 会将源码打包,并放到内存中, 可以通过浏览器地址访问
- Server.js. webpackdevMiddleware是webpackdevServer中专门来处理打包的具体位置的, 打包的文件输出路径从dist改成服务器内容路径, 不用每次都实际创建文件, 而是输出到内存里, 请求路径 localhost:8080的目标地址也指向内存路径.
// webpack-dev-server -> Server.js
var webpackDevMiddleware = require("webpack-dev-middleware");
this.middleware = webpackDevMiddleware(compiler, options);
- middleare.js, 通过
Shared
函数来处理保存路径问题, 在执行Shared
函数时- 把context的fs替换了. fs里面应该有 inputFileSystem输入文件的地址 , outputFileSystem文件输出路径, build时是文件的输出路径,在这里改成了内存地址.
// middleare.js
var Shared = require("./lib/Shared");
// 在这一步就直接把context的fs替换了. fs里面应该有 inputFileSystem , outputFileSystem文件输出路径, 一般
var shared = Shared(context);
shared.handleRequest(filename, processRequest, req);
webpackDevMiddleware.waitUntilValid = shared.waitUntilValid;
webpackDevMiddleware.invalidate = shared.invalidate;
webpackDevMiddleware.close = shared.close;
- Shared.js.
share.setFs(context.compiler);
就是改变fs(outputFileSystem)的操作. 通过 MemoryFileSystem 生成内存地址.
var MemoryFileSystem = require("memory-fs");
share.setFs(context.compiler);
// 修改 webpack的fs, fileSystem, 修改webpack默认的文件夹路径, 包括输出路径, 服务器请求路径
setFs: function(compiler) {
if(typeof compiler.outputPath === "string" && !pathIsAbsolute.posix(compiler.outputPath) && !pathIsAbsolute.win32(compiler.outputPath)) {
throw new Error("`output.path` needs to be an absolute path or `/`.");
}
// store our files in memory
var fs;
var isMemoryFs = !compiler.compilers && compiler.outputFileSystem instanceof MemoryFileSystem;
if(isMemoryFs) {
fs = compiler.outputFileSystem;
} else {
fs = compiler.outputFileSystem = new MemoryFileSystem();
}
context.fs = fs;
},
- memory-fs.js , 在这里是实际将文件内容保存到内存的地方. 创建的
MemoryFileSystem
对象会替换fs,- node有fs对象, 可以创建,读取,修改文件,创建,读取,修改文件夹.
const fs = require("fs")
- webpack有this.fs是对node的fs的封装(大概,不确定), 应该是加了前后置的操作, this.fs也可以 创建,读取,修改文件,创建,读取,修改文件夹
- 在开发模式中,
MemoryFileSystem
对象创建的实例机会替换掉webpack的fs, 使webpack在开发模式中,创建,读取修改文件时,不用node的fs而是用MemoryFileSystem
- MemoryFileSystem对象有node的fs对象上的所有方法,比如
readFile
,readFileSync
,writeFile
,rmdirSync
可以保证替换掉fs后this.fs还能正常运行. - 所谓的把文件保存到内存中就是指, 把文件内容保存到MemoryFileSystem的data属性里.data的结构大概是下面的情况.
- 当使用this.fs.readFile(url, (content)=>{}) 方法获取文件内容时, 通过data[url]获取到文件对象, 可以查看文件大小,文件内容. 将文件内容content返回.
- 当使用this.fs.rmdirSync(url)时, 通过查找data下面的路径, 把相关路径对象删除即可.
- 所谓的把文件内容放到内存里就是把打包好的文件的字符串内容, 创建一个对象,保存起来.即可.
- node有fs对象, 可以创建,读取,修改文件,创建,读取,修改文件夹.
// memory-fs.js
// MemoryFileSystem对象
["stat", "readdir", "mkdirp", "mkdir", "rmdir", "unlink", "readlink"].forEach(function(fn) {
MemoryFileSystem.prototype[fn] = function(path, callback) {
try {
var result = this[fn + "Sync"](path);
} catch(e) {
return callback(e);
}
return callback(null, result);
};
});
MemoryFileSystem.prototype.exists = function(path, callback) {
return callback(this.existsSync(path));
}
MemoryFileSystem.prototype.readFile = function(path, optArg, callback) {};
MemoryFileSystem.prototype.readFileSync = function(_path, encoding) {};
MemoryFileSystem.prototype.writeFile = function (path, content, encoding, callback) {};
MemoryFileSystem.prototype.rmdirSync = function(_path) {
return this._remove(_path, "Directory", isDir);
};
// MemoryFileSystem的data的结构, middleware中的内容是 Buffer形式存在, content.toString() => 就是要用的字符串
{
G: {
"webpack-learn": {
"vue2-elm-master": {
"dist": {
src: {
"index.js": Buffer(xxxxx)
},
"index.html": Buffer(xxxxx)
}
}
}
}
}
- 浏览器请求文件的过程
middleware.js
- 浏览器发出请求"http://localhost:8000/", 由于地址末尾没有文件名, 使用默认文件名即"http://localhost:8000/index.html"
- middleware监控8000端口,获取到请求, 拿到请求地址url"index.html"
- 根据webpack中的compiler保存的项目绝对路径
G\webpack-learn\vue2-elm-master
, 和 "publicPath"项目相对路径, 和文件名"index.html",
拼接处index.html的绝对路径 path =G\webpack-learn\vue2-elm-master\dist\index.html
,
var filename = getFilenameFromUrl(context.options.publicPath, context.compiler, req.url);
- 用''分割path,获取index.html的路径数组
['G', 'webpack-learn','vue2-elm-master', 'dist', 'index.html']
, - 在用这个路径数组,从 MemoryFileSystem的data(上一段落)里找对应文件, 拿到buffer后, Buffer.toString()即可获得index.html的文件内容
- 设置header, 比如content-type, content-length, 等.
res.setHeader("Content-Type", contentType);
res.setHeader("Content-Length", content.length);
- contentType的获取, contentType是根据文件名获取的, 在mime.js中维护了一个
types.json
.里面保存了所有文件后缀对应的contentType,
{
// contentType: [文件后缀]
"text/html":["html","htm","shtml"],
"application/json":["json","map"],
}
- 返回内容.
res.statusCode = res.statusCode || 200;
if(res.send) res.send(content);
10. webpack-dev-server的middleware处理浏览器请求总结.
- express监控服务器请求有两种方式,
- 监控请求
app.get("/index")
; - 中间件监控
app.use(()=>{})
; 中间件监控,不设置监控路径的话可以处理所有的请求
- 监控请求
- webpack-dev-server就是通过中间件, 监控到浏览器所有的请求.
- 修改webpack的fs为MemoryFileSystem,并将构建打包结构全部存储到内存中(用路径方式保存到一个对象里,文件内容就是属性值,buffer格式)
- 实现请求中间件, 用于处理所有资源请求, 并到内存中查询文件返回.
hmr热更新
html5服务器发送事件EventSource.
- HTML5 服务器发送事件(server-sent event)允许网页获得来自服务器的更新。
- h5方面:
new EventSource("getList")
会发送一个请求, 请求发送后, 不会断开, 而是每隔一段时间(比如10秒),服务器就会推送内容回来
h5可以在source.onmessage
事件中获取到推送内容.
// 发送 localhost:8000/getList请求.
var source=new EventSource("getList");
source.onmessage=function(event){
document.getElementById("result").innerHTML+=event.data + "<br>";
};
- 服务器方面, 针对getList请求,需要设置header:
{'Content-Type':'text/event-stream', 'Cache-Control': 'no-cache', 'Connection':'keep-alive'}
, 然后每隔10s推送一部分内容.h5方面即可获取.
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
if (req.url === '/getList') {
// 设置header
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
setInterval(() => {
// 此处格式不对, 浏览器无法无法获取发送内容.
res.write(`data: ${new Date().toISOString()}\n`);
}, 1000*10);
}
}).listen(3001, () => {
console.log('Server is running on port 3001');
});
- HMR热更新就是用
webpack-hot-middleware
开了一个中间件, 设置了一个EventSource
请求. 每当监控到源文件有变化时
就重新打包一次, 或者定时更新(比如5秒), 然后通过EventSource
请求把变化内容发送给客户端浏览器.
注意这是webpack@1的实现方式, 后续的webpack的实现方式暂时未知.
以下是webpack的EventSource请求例子
EventSource事件的发送方式
HMR 热更新的实现机制
- 需要客户端和服务端同事配合支线( HotModuleReplacementPlugin 和 webpack-hot-middleware 连动使用)
- 客户端和服务端双向通信机制复杂.
webpack.dev.conf.js中
- 在webpack的plugins里添加hotModuleReplacementPlugin,
new webpack.HotModuleReplacementPlugin()
- webpack的entry属性可以是数组.
entry: ['app.js', './build/dev-client']
这两块代码都会被打包到index.js中. 挨个执行.
// 给entry添加入口, './build/dev-client'是把 'webpack-hot-middleware/client'里的函数加入到入口中.
Object.keys(baseWebpackConfig.entry).forEach(function(name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
- './build/dev-client'是把 'webpack-hot-middleware/client'里的函数加入到entry中.
webpack-hot-middleware/client.js
- 在这里初始化了EventSource的请求地址, 超时时间, 频率等, 也就是
options
. processMessage
根据 webpack-hot-middleware 传过来的obj.action
的值,做响应处理- 如果action是"sync", 则用
processUpdate
更新内容. - 由于client.js已经被注入到index.js即项目的入口文件里, 所以上述代码会在浏览器中执行. 所以才能动态的直接变更响应dom结构.
// 基础配置
var options = {
path: '/__webpack_hmr',
timeout: 20 * 1000,
overlay: true,
reload: false,
log: true,
warn: true,
name: '',
autoConnect: true,
overlayStyles: {},
overlayWarnings: false,
ansiColors: {},
};
function processMessage(obj) {
switch (obj.action) {
case 'building':
// log
break;
case 'built':
// log fall through
case 'sync':
// 判断是否要更新
if (applyUpdate) {
// 更新内容
processUpdate(obj.hash, obj.modules, options);
}
break;
default:
if (customHandler) {
customHandler(obj);
}
}
}
connect-history-api-fallback
connect-history-api-fallback是一个用于处理单页应用(SPA)路由问题的中间件,它主要用于解决在使用HTML5 History API时,用户直接访问子页面的问题。
在单页应用中,我们通常使用HTML5的History API来管理URL的变化,而不是通过服务器重新加载整个页面。然而,当用户直接访问一个子页面的URL时,如果没有相应的路由处理逻辑,浏览器会尝试向服务器请求该资源,但服务器可能无法找到对应的资源,从而导致404错误。
为了解决这个问题,我们可以使用connect-history-api-fallback中间件。它会拦截所有非API请求,并重定向到指定的入口文件(通常是index.html)。这样,无论用户访问哪个URL,都会被重定向到index.html,然后由前端路由库(如React Router、Vue Router等)接管并正确渲染相应的组件。
const express = require('express');
const history = require('connect-history-api-fallback');
const app = express();
app.use(history());
// 其他中间件和路由配置
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
标签:vue,cli,res,app,中间件,js,webpack,fs,原理
From: https://www.cnblogs.com/bridge7839/p/18572998