还能把浏览器当作 Web 服务器?骚操作,学废了~
楔子
什么?还能把浏览器当作 Web 服务器?
闲话少说,直接干货!
整体思路:PWA 中用于缓存文件的 server workers 可以动态生成新文件,并通过 fetch 事件,将它们发送至浏览器!
- 不熟悉 PWA 的朋友们可简单了解如下:
PWA(Progressive Web Apps) 翻译为 渐进式网页应用,它是一种构建 Web 应用程序的新理念,涉及 一些 特定的模式,API 和其他功能。
它能实现传统 web 所不能做到的:离线工作、可安装、易于同步、可以发送推送通知等;
- 不熟悉 server workers 的朋友们可简单了解如下:
server workers 就是一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker,那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。
所以,通过 server workers 可以发送文件至浏览器!
工具
- 我们还要借助:somorphic-git
somorphic-git 是 git 的纯 JavaScript 实现,适用于 Node 和浏览器环境(包括WebWorkers 和 ServiceWorkers);
它可以用于读写 git 库,以及从 Github 获取和推送。
- 我们还要借助 BrowserFS
BrowserFS 与 Webpack 类似,也是模块打包工具;
它的特点:
- 基于流式(stream)思想设计
- 可以通过command line,也可以通过API来使用
- 仅处理javascript
- 模块化的逆过程,但是推动着模块化的更好发展
- 内置了一些 node core module
- node模块可以在浏览器端使用,是 同构应用 的有力武器
- 我们还要借助 Lightning-fs
它让浏览器文件读写更加快速,快如闪电⚡~
- 最后还需要 indexedDB
这个相信大家并不陌生,indexedDB 可以实现在客户端存储大量的结构化数据~~
实现
代码实现:
/**@license
* ___ ___ _____ __ __ _ _____ _ _
* / __|_ _|_ _| \ \ / /__| |__ |_ _|__ _ _ _ __ (_)_ _ __ _| |
* | (_ || | | | \ // / -_) '_ \ | |/ -_) '_| ' | | ' / _` | |
* ___|___| |_| _/_/___|_.__/ |_|___|_| |_|_|_|_|_||___,_|_|
*
* this is service worker and it's part of GIT Web terminal
*
* Copyright (c) 2018 Jakub Jankiewicz <http://jcubic.pl/me>
* Released under the MIT license
*
*/
/* global BrowserFS, Response, setTimeout, fetch, Blob, Headers */
self.importScripts('https://cdn.jsdelivr.net/npm/browserfs');
self.addEventListener('install', self.skipWaiting);
self.addEventListener('activate', self.skipWaiting);
self.addEventListener('fetch', function (event) {
let path = BrowserFS.BFSRequire('path');
let fs = new Promise(function(resolve, reject) {
BrowserFS.configure({ fs: 'IndexedDB', options: {} }, function (err) {
if (err) {
reject(err);
} else {
resolve(BrowserFS.BFSRequire('fs'));
}
});
});
event.respondWith(fs.then(function(fs) {
return new Promise(function(resolve, reject) {
function sendFile(path) {
fs.readFile(path, function(err, buffer) {
if (err) {
err.fn = 'readFile(' + path + ')';
return reject(err);
}
var ext = path.replace(/.*./, '');
var mime = {
'html': 'text/html',
'json': 'application/json',
'js': 'application/javascript',
'css': 'text/css'
};
var headers = new Headers({
'Content-Type': mime[ext]
});
resolve(new Response(buffer, {headers}));
});
}
var url = event.request.url;
var m = url.match(/__browserfs__(.*)/);
function redirect_dir() {
return resolve(Response.redirect(url + '/', 301));
}
function serve() {
fs.stat(path, function(err, stat) {
if (err) {
return resolve(textResponse(error404Page(path)));
}
if (stat.isFile()) {
sendFile(path);
} else if (stat.isDirectory()) {
if (path.substr(-1, 1) !== '/') {
return redirect_dir();
}
fs.readdir(path, function(err, list) {
if (err) {
err.fn = 'readdir(' + path + ')';
return reject(err);
}
var len = list.length;
if (list.includes('index.html')) {
sendFile(path + '/index.html');
} else {
listDirectory({fs, path, list}).then(function(list) {
resolve(textResponse(fileListingPage(path, list)));
}).catch(reject);
}
});
}
});
}
if (m) {
var path = m[1];
if (path === '') {
return redirect_dir();
}
console.log('serving ' + path + ' from browserfs');
serve();
} else {
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
return;
}
//request = credentials: 'include'
fetch(event.request).then(resolve).catch(reject);
}
});
}));
});
// -----------------------------------------------------------------------------
function listDirectory({fs, path, list}) {
return new Promise(function(resolve, reject) {
var items = [];
(function loop() {
var item = list.shift();
if (!item) {
return resolve(items);
}
fs.stat(path + '/' + item, function(err, stat) {
if (err) {
err.fn = 'stat(' + path + '/' + item + ')';
return reject(err);
}
items.push(stat.isDirectory() ? item + '/' : item);
loop();
});
})();
});
}
// -----------------------------------------------------------------------------
function textResponse(string, filename) {
var blob = new Blob([string], {
type: 'text/html'
});
return new Response(blob);
}
// -----------------------------------------------------------------------------
function fileListingPage(path, list) {
var output = [
'<!DOCTYPE html>',
'<html>',
'<body>',
`<h1>BrowserFS ${path}</h1>`,
'<ul>'
];
if (path.match(/^/(.*/)/)) {
output.push('<li><a href="..">..</a></li>');
}
list.forEach(function(name) {
output.push('<li><a href="' + name + '">' + name + '</a></li>');
});
output = output.concat(['</ul>', '</body>', '</html>']);
return output.join('\n');
}
// -----------------------------------------------------------------------------
function error404Page(path) {
var output = [
'<!DOCTYPE html>',
'<html>',
'<body>',
'<h1>404 File Not Found</h1>',
`<p>File ${path} not found in browserfs`,
'</body>',
'</html>'
];
return output.join('\n');
}
初始化 service worker 如下:
if ('serviceWorker' in navigator) {
var scope = location.pathname.replace(/\/[^\/]+$/, '/');
if (!scope.match(/__browserfs__/)) {
navigator.serviceWorker.register('sw.js', {scope})
.then(function(reg) {
reg.addEventListener('updatefound', function() {
var installingWorker = reg.installing;
console.log('A new service worker is being installed:',
installingWorker);
});
// registration worked
console.log('Registration succeeded. Scope is ' +
reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
}
测试
原作者大佬 Jakub T. Jankiewicz 还搞了个在线测试 Demo GIT Web Terminal,除了 NB 我还能说什么呢?
调用测试:
vi test.txt
i Hello World :wq
view test.txt
查看返回:
有一说一,这个方向的尝试还是很顶的~~
增强 Web 能力,吾辈义不容辞!(●’◡’●)
OK,以上就是本篇分享~ 撰文不易,点赞鼓励👍👍👍
本篇参考:
我是掘金安东尼,公众号同名,日拱一卒、日掘一金,再会~
- 点赞
- 收藏
- 关注作者
评论(0)