楔子
什么?还能把浏览器当作 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,以上就是本篇分享~ 撰文不易,点赞鼓励👍👍👍
本篇参考:
我是掘金安东尼,公众号同名,日拱一卒、日掘一金,再会~