├── .gitignore ├── .npmignore ├── LICENSE ├── README-zh_CN.md ├── README.md ├── assets ├── vfs-ls.png └── vfs-mount.png ├── demo ├── .env ├── .gitignore ├── README.md ├── build │ ├── asset-manifest.json │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.704f33ea7d21dde71da5800dfbf88209.js │ ├── robots.txt │ ├── service-worker.js │ └── static │ │ ├── css │ │ ├── 2.63bd53f5.chunk.css │ │ ├── 2.63bd53f5.chunk.css.map │ │ ├── main.b6963092.chunk.css │ │ └── main.b6963092.chunk.css.map │ │ ├── js │ │ ├── 2.7af39487.chunk.js │ │ ├── 2.7af39487.chunk.js.map │ │ ├── main.4956af3b.chunk.js │ │ ├── main.4956af3b.chunk.js.map │ │ ├── runtime~main.909208d1.js │ │ └── runtime~main.909208d1.js.map │ │ └── media │ │ ├── devopicons.e19892df.woff2 │ │ ├── file-icons.11d1a239.woff2 │ │ ├── fontawesome.af7ae505.woff2 │ │ ├── mfixx.0a32a802.woff2 │ │ └── octicons.de59a972.woff2 ├── config │ ├── env.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── modules.js │ ├── paths.js │ ├── pnpTs.js │ ├── webpack.config.js │ └── webpackDevServer.config.js ├── package.json ├── public │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── scripts │ ├── build.js │ ├── start.js │ └── test.js ├── src │ ├── component │ │ ├── details │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ ├── tree │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ └── util.ts │ │ └── upload-form │ │ │ └── index.tsx │ ├── file-icons-js.css │ ├── index.scss │ ├── index.tsx │ ├── page │ │ ├── index.scss │ │ └── index.tsx │ ├── react-app-env.d.ts │ ├── serviceWorker.js │ └── util │ │ └── index.ts └── tsconfig.json ├── package.json ├── src ├── index.ts ├── inode.ts ├── types.ts └── zipVfs.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | *.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | node_modules 3 | src 4 | .gitignore 5 | .npmignore 6 | package.json 7 | tsconfig.json 8 | webpack.config.js 9 | yarn.lock 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 John 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # vfs-frontend 2 | 3 | vfs-frontend 是一个纯前端实现的简易虚拟文件系统,只需传递带有 zip 属性的 blob 二进制数据即可在前端构建出一个虚拟文件系统,解析 blob 数据用的是 [JSZip](https://github.com/Stuk/jszip) 这个库 4 | 5 | [English](README.md) | 简体中文 6 | 7 | # demo 8 | 9 | https://ricbet.github.io/vfs-frontend/ 10 | 11 | # 安装 12 | 13 | > yarn add vfs-frontend 14 | 15 | # 如何使用 16 | 17 | ```typescript 18 | const vfsService = new ZipVFSService(); 19 | 20 | // 仅接收 blob 数据 21 | vfsService.mount(blob).then(async () => { 22 | // do it 23 | }); 24 | 25 | ``` 26 | 此时 vfsService 已经被挂载了, 例如 27 | 28 | mount 29 | 30 | 根目录默认为 '/', 你可以使用 ls 方法查看某个目录下的所有文件, 例如 31 | 32 | ```typescript 33 | vfsService.ls('/').then((data: Inode[]) => { 34 | // do it 35 | }) 36 | ``` 37 | 38 | ls 39 | 40 | 🌈enjoy😊🌈 41 | 42 | # API 43 | 44 | ```typescript 45 | interface IVfsable { 46 | mount( 47 | data: T, 48 | options?: { 49 | name: string; 50 | } 51 | ): Promise; 52 | 53 | ls(path: string): Promise; 54 | 55 | read(path: string, encode?: string): Promise; 56 | 57 | mkdir(path: string): Promise; 58 | 59 | touch(path: string): Promise; 60 | 61 | write(path: string, options: { isAppend: boolean }, content: string): Promise; 62 | 63 | exist(path: string): Promise; 64 | 65 | cp(source: string, copyend: string): Promise; 66 | 67 | mv(source: string, target: string): Promise; 68 | 69 | rm(path: string, options: { isForce: boolean; isRecursive: boolean }): Promise; 70 | 71 | isMount: () => boolean; 72 | 73 | getBlob: () => Promise; 74 | } 75 | 76 | ``` 77 | 78 | # LICENSE 79 | 80 | MIT 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vfs-frontend 2 | 3 | VFS-frontend is a simple virtual file system implemented purely on the frontend, where a virtual file system is built by simply passing blob binary data with a zip attribute,The [JSZip](https://github.com/Stuk/jszip) library is used for parsing blob data 4 | 5 | English | [简体中文](README-zh_CN.md) 6 | 7 | # demo 8 | 9 | https://ricbet.github.io/vfs-frontend/ 10 | 11 | 12 | # install 13 | 14 | > yarn add vfs-frontend 15 | 16 | # how to use 17 | 18 | ```typescript 19 | const vfsService = new ZipVFSService(); 20 | 21 | vfsService.mount(blob).then(async () => { 22 | // do it 23 | }); 24 | 25 | ``` 26 | Now, vfsService has been mounted, for example 27 | 28 | mount 29 | 30 | The root directory defaults to '/', you can use the 'ls' method to view all files in a directory, for example 31 | 32 | ```typescript 33 | vfsService.ls('/').then((data: Inode[]) => { 34 | // do it 35 | }) 36 | ``` 37 | 38 | ls 39 | 40 | 🌈enjoy😊🌈 41 | 42 | # api 43 | 44 | ```typescript 45 | interface IVfsable { 46 | mount( 47 | data: T, 48 | options?: { 49 | name: string; 50 | } 51 | ): Promise; 52 | 53 | ls(path: string): Promise; 54 | 55 | read(path: string, encode?: string): Promise; 56 | 57 | mkdir(path: string): Promise; 58 | 59 | touch(path: string): Promise; 60 | 61 | write(path: string, options: { isAppend: boolean }, content: string): Promise; 62 | 63 | exist(path: string): Promise; 64 | 65 | cp(source: string, copyend: string): Promise; 66 | 67 | mv(source: string, target: string): Promise; 68 | 69 | rm(path: string, options: { isForce: boolean; isRecursive: boolean }): Promise; 70 | 71 | isMount: () => boolean; 72 | 73 | getBlob: () => Promise; 74 | } 75 | 76 | ``` 77 | 78 | # license 79 | 80 | MIT 81 | -------------------------------------------------------------------------------- /assets/vfs-ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ricbet/vfs-frontend/6a78439f249101689e841c2306dc06f1ba1114c4/assets/vfs-ls.png -------------------------------------------------------------------------------- /assets/vfs-mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ricbet/vfs-frontend/6a78439f249101689e841c2306dc06f1ba1114c4/assets/vfs-mount.png -------------------------------------------------------------------------------- /demo/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | # /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | !lib -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ricbet/vfs-frontend/6a78439f249101689e841c2306dc06f1ba1114c4/demo/README.md -------------------------------------------------------------------------------- /demo/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.b6963092.chunk.css", 4 | "main.js": "./static/js/main.4956af3b.chunk.js", 5 | "main.js.map": "./static/js/main.4956af3b.chunk.js.map", 6 | "runtime~main.js": "./static/js/runtime~main.909208d1.js", 7 | "runtime~main.js.map": "./static/js/runtime~main.909208d1.js.map", 8 | "static/css/2.63bd53f5.chunk.css": "./static/css/2.63bd53f5.chunk.css", 9 | "static/js/2.7af39487.chunk.js": "./static/js/2.7af39487.chunk.js", 10 | "static/js/2.7af39487.chunk.js.map": "./static/js/2.7af39487.chunk.js.map", 11 | "index.html": "./index.html", 12 | "precache-manifest.704f33ea7d21dde71da5800dfbf88209.js": "./precache-manifest.704f33ea7d21dde71da5800dfbf88209.js", 13 | "service-worker.js": "./service-worker.js", 14 | "static/css/2.63bd53f5.chunk.css.map": "./static/css/2.63bd53f5.chunk.css.map", 15 | "static/css/main.b6963092.chunk.css.map": "./static/css/main.b6963092.chunk.css.map", 16 | "static/media/file-icons-js.css": "./static/media/octicons.de59a972.woff2" 17 | } 18 | } -------------------------------------------------------------------------------- /demo/build/index.html: -------------------------------------------------------------------------------- 1 | vfs demo
-------------------------------------------------------------------------------- /demo/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demo/build/precache-manifest.704f33ea7d21dde71da5800dfbf88209.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "0c4820010c203a2ed4f3c245855bcd30", 4 | "url": "./index.html" 5 | }, 6 | { 7 | "revision": "033e95d0ab7be2d8c302", 8 | "url": "./static/css/2.63bd53f5.chunk.css" 9 | }, 10 | { 11 | "revision": "b79131827637a0c0a79f", 12 | "url": "./static/css/main.b6963092.chunk.css" 13 | }, 14 | { 15 | "revision": "033e95d0ab7be2d8c302", 16 | "url": "./static/js/2.7af39487.chunk.js" 17 | }, 18 | { 19 | "revision": "b79131827637a0c0a79f", 20 | "url": "./static/js/main.4956af3b.chunk.js" 21 | }, 22 | { 23 | "revision": "2ba3c37f2a02ac87aa40", 24 | "url": "./static/js/runtime~main.909208d1.js" 25 | }, 26 | { 27 | "revision": "e19892df121e7f85c61754c54bbb7951", 28 | "url": "./static/media/devopicons.e19892df.woff2" 29 | }, 30 | { 31 | "revision": "11d1a2390613abf202bfd2ebbd5c4500", 32 | "url": "./static/media/file-icons.11d1a239.woff2" 33 | }, 34 | { 35 | "revision": "af7ae505a9eed503f8b8e6982036873e", 36 | "url": "./static/media/fontawesome.af7ae505.woff2" 37 | }, 38 | { 39 | "revision": "0a32a80243e05284a5d741c5dc093f31", 40 | "url": "./static/media/mfixx.0a32a802.woff2" 41 | }, 42 | { 43 | "revision": "de59a97248b44599e6747a27a943f738", 44 | "url": "./static/media/octicons.de59a972.woff2" 45 | } 46 | ]); -------------------------------------------------------------------------------- /demo/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /demo/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "./precache-manifest.704f33ea7d21dde71da5800dfbf88209.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("./index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^\/?]+\.[^\/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /demo/build/static/css/main.b6963092.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}i{font-style:normal}.fa-file:before{content:"\f15b"}.fa-file-o:before,.fa-file:before{font-family:FontAwesome;font-size:13px;top:1px}.fa-file-o:before{content:"\f016"}.page{background-color:#fff;height:100vh}.page .content{display:flex}.page .content .left{background-color:#fff;padding:20px;min-width:260px;width:260px}.page .content .file-details{flex:1 1;max-width:calc(100% - 500px)}.page .feact-url{border:1px solid #ccc;padding:12px;border-radius:4px;margin-top:20px}.details-main{overflow:auto;width:100%;height:100%;background-color:#fff}.details-main .pre-content{padding:20px 15px} 2 | /*# sourceMappingURL=main.b6963092.chunk.css.map */ -------------------------------------------------------------------------------- /demo/build/static/css/main.b6963092.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.scss","file-icons-js.css"],"names":[],"mappings":"AAAA,KACI,QAAS,CACT,mIAC8C,CAC9C,kCAAmC,CACnC,iCAAkC,CAGtC,KACI,uEAA+E,CAGnF,EACI,iBAAkB,CCXtB,gBAGI,eAGJ,CACA,kCANI,uBAAwB,CAGxB,cAAe,CACf,OAQJ,CANA,kBAGI,eAGJ,CDfA,MACI,qBAAsB,CACtB,YAAa,CAFjB,eAIQ,YAAa,CAJrB,qBAMY,qBAAsB,CACtB,YAAa,CACb,eAAgB,CAChB,WAAY,CATxB,6BAYY,QAAO,CACP,4BAA6B,CAbzC,iBAiBQ,qBAAsB,CACtB,YAAa,CACb,iBAAkB,CAClB,eAAgB,CApBxB,cACI,aAAc,CACd,UAAW,CACX,WAAY,CACZ,qBAAsB,CAJ1B,2BAMQ,iBAAkB","file":"main.b6963092.chunk.css","sourcesContent":[".details-main {\n overflow: auto;\n width: 100%;\n height: 100%;\n background-color: #fff;\n .pre-content {\n padding: 20px 15px;\n }\n}\n","@import url(\"~file-icons-js/css/style.css\");\n\n.fa-file:before {\n font-family: FontAwesome;\n font-size: 13px;\n content: \"\\f15b\";\n font-size: 13px;\n top: 1px;\n}\n.fa-file-o:before {\n font-family: FontAwesome;\n font-size: 13px;\n content: \"\\f016\";\n font-size: 13px;\n top: 1px;\n}\n"]} -------------------------------------------------------------------------------- /demo/build/static/js/main.4956af3b.chunk.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonpvfs-demo"]=window["webpackJsonpvfs-demo"]||[]).push([[0],{165:function(e,t,n){e.exports=n(371)},170:function(e,t,n){},364:function(e,t,n){},368:function(e,t,n){},369:function(e,t,n){},371:function(e,t,n){"use strict";n.r(t);var a=n(1),r=n.n(a),i=n(6),c=n.n(i),o=(n(170),n(171),n(104)),s=(n(173),n(158)),l=(n(105),n(48)),u=(n(106),n(160)),m=(n(177),n(103)),f=n(42),p=n.n(f),h=n(77),d=n(30),v=(n(373),n(101)),b=(n(372),n(159)),E=(n(185),n(5)),g=function(e){var t=e.onUploadChange;return r.a.createElement("div",{className:"upload-from-main"},r.a.createElement(v.a,{layout:"vertical"},r.a.createElement(v.a.Item,null,r.a.createElement(b.a.Dragger,{name:"zipFiles",accept:".zip,.tar",beforeUpload:function(e){return t(new Blob([e],{type:"text/plain"})),!1},showUploadList:!1},r.a.createElement("p",{className:"ant-upload-drag-icon"},r.a.createElement(E.a,{type:"inbox"})),r.a.createElement("p",{className:"ant-upload-text"},"\u5355\u51fb\u6216\u62d6\u52a8\u6587\u4ef6\u5230\u6b64\u533a\u57df\u4e0a\u4f20"),r.a.createElement("p",{className:"ant-upload-hint"},"\u4ec5\u652f\u6301 ZIP \u538b\u7f29\u5305")))))},w=n(73),y=n(161),j=(n(362),n(102)),O=n(153),x=n(154),D=n(162),N=n(155),k=n(163),S=function(e){function t(){var e;return Object(O.a)(this,t),(e=Object(D.a)(this,Object(N.a)(t).call(this))).children=void 0,e.init(),e}return Object(k.a)(t,e),Object(x.a)(t,[{key:"init",value:function(){this.children=[]}},{key:"setChildren",value:function(e){return this.children=e||[],this}}]),t}(w.Inode),C=function(e){return(new S).setName(e.name).setDate(e.date||new Date).setDosPermissions(e.dosPermissions).setIsDir(e.isDir).setParentPath(e.parentPath).setPath(e.path).setUnixPermissions(e.unixPermissions)},P=(n(364),j.a.TreeNode),T=j.a.DirectoryTree,A=n(367),I=function(e){var t=e.data,n=e.vfsService,i=e.onLaunchFileDetails,c=Object(a.useState)([]),o=Object(d.a)(c,2),s=o[0],l=o[1];Object(a.useEffect)(function(){Array.isArray(t)&&l(t.map(function(e){return C(e)}))},[t]);var u=function(e){n&&n.ls(e.path).then(function(t){var n=t.map(function(e){return C(e)});e.setChildren(function(e){var t=e.slice(0);return function e(t){if(t.length<=1)return t;for(var n,a=t.splice(Math.floor(t.length/2),1)[0],r=[],i=[],c=0;c0?r.push(o):i.push(o)):i.push(o)}return e(r).concat([a],e(i))}(t)}(n)),l(Object(y.a)(s))})},m=function(e){n&&i&&n.read(e.path).then(function(e){"string"===typeof e&&i(e)})};return r.a.createElement("div",{className:"tree-comp",style:{overflow:"auto",height:"100%",maxWidth:"300px",minWidth:"240px"}},s.length?r.a.createElement(T,{onSelect:function(e){if(n){var t,a=Object(d.a)(e,1)[0];if(n.exist(a)){var r=(t=a,function e(t){return t.reduce(function(t,n){return Array.isArray(n.children)&&n.children.length>0?t.concat(e(n.children)):t.concat(n)},[])}(s).find(function(e){return e.path===t}));if(!r)return;r.isDir?u(r):m(r)}}}},function e(t){return t.map(function(t){return r.a.createElement(P,{icon:t.isDir?null:r.a.createElement("i",{className:A.getClassWithColor(t.name)||"fa-file-o"}),title:t.name,key:t.path,dataRef:t,isLeaf:!t.isDir},e(t.children))})}(s)):null)},U=(n(368),n(369),function(e){var t=e.content;return r.a.createElement("div",{className:"details-main"},r.a.createElement("div",{className:"pre-content"},r.a.createElement("pre",null,t)))}),W=new w.ZipVFSService,z=function(){var e=Object(a.useState)([]),t=Object(d.a)(e,2),n=t[0],i=t[1],c=Object(a.useState)(),f=Object(d.a)(c,2),v=f[0],b=f[1],E=Object(a.useState)(""),w=Object(d.a)(E,2),y=w[0],j=w[1],O=Object(a.useState)(!1),x=Object(d.a)(O,2),D=x[0],N=x[1],k=Object(a.useState)(""),S=Object(d.a)(k,2),C=S[0],P=S[1],T=function(){var e=Object(h.a)(p.a.mark(function e(t){return p.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:W.mount(t).then(Object(h.a)(p.a.mark(function e(){var t;return p.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,W.ls("/");case 2:t=e.sent,i(t||[]),b(W),window.vfsService=W;case 6:case"end":return e.stop()}},e)})));case 1:case"end":return e.stop()}},e)}));return function(t){return e.apply(this,arguments)}}(),A=function(){N(!0),fetch(y,{headers:{"content-type":"application/zip"}}).then(function(){var e=Object(h.a)(p.a.mark(function e(t){var n;return p.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(!t||200!==t.status||"application/zip"!==t.headers.get("content-type")){e.next=5;break}return e.next=3,t.blob();case 3:n=e.sent,T(n);case 5:case"end":return e.stop()}},e)}));return function(t){return e.apply(this,arguments)}}()).finally(function(){N(!1)})};return r.a.createElement("div",{className:"page"},r.a.createElement(o.a,{style:{height:"100vh"}},r.a.createElement(o.a.Content,{className:"content"},r.a.createElement("div",{className:"left"},r.a.createElement(g,{onUploadChange:T}),r.a.createElement(m.a.Text,{style:{textAlign:"center",width:"100%",display:"inline-block"}},"or"),r.a.createElement("div",{className:"feact-url"},r.a.createElement(m.a.Text,null,"\u8fd4\u56de Blob \u6570\u636e\u683c\u5f0f\u7684\u63a5\u53e3"),r.a.createElement(u.a,{style:{marginTop:"10px"},value:y,onChange:function(e){return j(e.target.value)}}),r.a.createElement(l.a,{type:"primary",style:{width:"100%",marginTop:"10px"},onClick:A},"\u786e\u5b9a"))),r.a.createElement("div",{className:"right"},r.a.createElement(s.a,{spinning:D,style:{marginTop:"20px"}},r.a.createElement(I,{data:n,vfsService:v,onLaunchFileDetails:function(e){P(e)}}))),r.a.createElement("div",{className:"file-details"},r.a.createElement(U,{content:C})))))};Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));c.a.render(r.a.createElement(z,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(e){e.unregister()})}},[[165,1,2]]]); 2 | //# sourceMappingURL=main.4956af3b.chunk.js.map -------------------------------------------------------------------------------- /demo/build/static/js/main.4956af3b.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["component/upload-form/index.tsx","component/tree/model.ts","component/tree/util.ts","component/tree/index.tsx","component/details/index.tsx","page/index.tsx","serviceWorker.js","index.tsx"],"names":["UploadForm","prop","onUploadChange","className","layout","Item","Dragger","name","accept","beforeUpload","e","Blob","type","showUploadList","TInode","children","init","this","c","Inode","generateInode","setName","setDate","date","Date","setDosPermissions","dosPermissions","setIsDir","isDir","setParentPath","parentPath","setPath","path","setUnixPermissions","unixPermissions","TreeNode","DirectoryTree","fileIcons","require","TreeComp","props","data","vfsService","onLaunchFileDetails","useState","inodeData","setInodeData","useEffect","Array","isArray","map","cur","expandDir","d","ls","then","llist","l","setChildren","sliceData","slice","quickSort","arr","length","pre","pivotFile","splice","Math","floor","left","right","i","currentFile","push","localeCompare","concat","handleSortFileList","checkFile","f","read","content","style","overflow","height","maxWidth","minWidth","onSelect","target","exist","curTree","flat","reduce","find","renderTreeNodes","item","icon","getClassWithColor","title","key","dataRef","isLeaf","Details","ZipVFSService","Page","treeData","setTreeData","treeVfss","setTreeVfss","blobUrl","setBlobUrl","isLoad","setIsLoad","currentFileDetails","setCurrentFileDetails","acceptUplodaChange","blob","a","mount","treelist","window","acquireBlobUrl","fetch","headers","responData","status","get","finally","Content","Text","textAlign","width","display","marginTop","value","onChange","onClick","spinning","Boolean","location","hostname","match","ReactDOM","render","document","getElementById","navigator","serviceWorker","ready","registration","unregister"],"mappings":"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","file":"static/js/main.4956af3b.chunk.js","sourcesContent":["import React from \"react\";\nimport { Form, Upload, Icon } from \"antd\";\nimport { RcFile } from \"antd/lib/upload/interface\";\n\ninterface UploadFormProps {\n onUploadChange: (arg: Blob) => void;\n}\n\nconst UploadForm = (prop: UploadFormProps): JSX.Element => {\n const { onUploadChange } = prop;\n\n const acceptUploadChange = (file: RcFile): boolean => {\n onUploadChange(new Blob([file], { type: \"text/plain\" }));\n return false;\n };\n\n return (\n
\n
\n \n acceptUploadChange(e)}\n showUploadList={false}\n >\n

\n \n

\n

单击或拖动文件到此区域上传

\n

仅支持 ZIP 压缩包

\n \n
\n {/* \n \n */}\n
\n
\n );\n};\n\nexport default UploadForm;\n","import { Inode } from \"vfs-frontend\";\n\nexport class TInode extends Inode {\n\n public children!: TInode[]\n\n constructor() {\n super();\n this.init()\n }\n\n public init(): void {\n this.children = []\n }\n\n public setChildren(c: TInode[]): this {\n this.children = c || []\n return this\n }\n\n}\n","import { TInode } from \"./model\";\n\nexport const generateInode = (e: TInode): TInode => {\n return new TInode()\n .setName(e.name)\n .setDate(e.date || new Date())\n .setDosPermissions(e.dosPermissions!)\n .setIsDir(e.isDir)\n .setParentPath(e.parentPath)\n .setPath(e.path)\n .setUnixPermissions(e.unixPermissions!);\n};\n\n// 文件排序\nexport const handleSortFileList = (data: TInode[]): TInode[] => {\n // 比较首字母ACS编码排序\n const sliceData = data.slice(0);\n const sortCharCode = (pre: TInode, cur: TInode): boolean => cur.name.localeCompare(pre.name) > 0;\n const quickSort = (arr: TInode[]): TInode[] => {\n if (arr.length <= 1) return arr;\n const pivotFile = arr.splice(Math.floor(arr.length / 2), 1)[0];\n const left = [];\n const right = [];\n for (let i = 0; i < arr.length; i++) {\n const currentFile = arr[i];\n if (currentFile.isDir === true && pivotFile.isDir === false) left.push(currentFile);\n else {\n if (currentFile.isDir === pivotFile.isDir) {\n sortCharCode(currentFile, pivotFile) ? left.push(currentFile) : right.push(currentFile);\n } else right.push(currentFile);\n }\n }\n return quickSort(left).concat([pivotFile], quickSort(right));\n };\n const result = quickSort(sliceData);\n return result;\n};\n\nexport const findTreeData = (source: TInode[], target: string): TInode | undefined => {\n const flat = (arr: TInode[]): TInode[] => {\n return arr.reduce(\n (pre: TInode[], cur) =>\n Array.isArray(cur.children) && cur.children.length > 0\n ? pre.concat(flat(cur.children))\n : pre.concat(cur),\n []\n );\n };\n const dimenList = flat(source);\n return dimenList.find(e => e.path === target);\n};\n","import React, { useEffect, useState } from \"react\";\nimport { Tree } from \"antd\";\nimport { TInode } from \"./model\";\nimport { ZipVFSService } from \"vfs-frontend\";\nimport { generateInode, handleSortFileList, findTreeData } from \"./util\";\nimport \"../../file-icons-js.css\";\n\nconst { TreeNode, DirectoryTree } = Tree;\nconst fileIcons = require(\"file-icons-js\");\n\ninterface PropsTreeComp {\n data: TInode[];\n vfsService?: ZipVFSService;\n onLaunchFileDetails?: (d: string) => void;\n}\n\nconst TreeComp = (props: PropsTreeComp): JSX.Element => {\n const { data, vfsService, onLaunchFileDetails } = props;\n\n const [inodeData, setInodeData] = useState([]);\n\n useEffect(() => {\n if (Array.isArray(data)) {\n setInodeData(data.map(cur => generateInode(cur)));\n }\n }, [data]);\n\n const selectNode = (e: string[]): void => {\n if (!vfsService) return;\n\n const [path] = e;\n\n if (vfsService.exist(path)) {\n const curTree = findTreeData(inodeData, path);\n if (!curTree) return;\n\n if (curTree.isDir) {\n expandDir(curTree);\n } else {\n checkFile(curTree);\n }\n }\n };\n\n const expandDir = (d: TInode) => {\n if (!vfsService) return;\n vfsService.ls(d.path).then(ls => {\n const llist = ls.map(l => generateInode(l as TInode));\n d.setChildren(handleSortFileList(llist));\n setInodeData([...inodeData]);\n });\n };\n\n const checkFile = (f: TInode) => {\n if (!vfsService || !onLaunchFileDetails) return;\n vfsService.read(f.path).then(content => {\n if (typeof content === \"string\") onLaunchFileDetails(content);\n });\n };\n\n const renderTreeNodes = (data: TInode[]): JSX.Element[] => {\n return data.map(item => {\n return (\n }\n title={item.name}\n key={item.path}\n dataRef={item}\n isLeaf={!item.isDir}\n >\n {renderTreeNodes(item.children)}\n \n );\n });\n };\n\n return (\n
\n {inodeData.length ? (\n {renderTreeNodes(inodeData)}\n ) : null}\n
\n );\n};\n\nexport default TreeComp;\n","import React from \"react\";\n\nimport \"./index.scss\";\n\ninterface PropsDetails {\n content: string;\n}\n\nconst Details = (props: PropsDetails): JSX.Element => {\n const { content } = props;\n return (\n
\n
\n
{content}
\n
\n
\n );\n};\n\nexport default Details;\n","import React, { useState } from \"react\";\nimport { Layout, Typography, Input, Button, Spin } from \"antd\";\nimport UploadForm from \"../component/upload-form\";\nimport { ZipVFSService, Inode } from \"vfs-frontend\";\nimport TreeComp from \"../component/tree\";\n\nimport \"./index.scss\";\nimport { TInode } from \"../component/tree/model\";\nimport Details from \"../component/details\";\n\nconst vfsService = new ZipVFSService();\n\nconst Page = () => {\n const [treeData, setTreeData] = useState([]);\n const [treeVfss, setTreeVfss] = useState();\n const [blobUrl, setBlobUrl] = useState(\"\");\n const [isLoad, setIsLoad] = useState(false);\n const [currentFileDetails, setCurrentFileDetails] = useState(\"\");\n\n const acceptUplodaChange = async (blob: Blob) => {\n vfsService.mount(blob).then(async () => {\n const treelist = await vfsService.ls(\"/\");\n setTreeData(treelist || []);\n setTreeVfss(vfsService);\n (window as any).vfsService = vfsService\n });\n };\n\n const onCurrentFileContent = (content: string) => {\n setCurrentFileDetails(content);\n };\n\n const acquireBlobUrl = () => {\n setIsLoad(true)\n fetch(blobUrl, {\n headers: { \"content-type\": \"application/zip\" },\n }).then(async responData => {\n if (\n responData &&\n responData.status === 200 &&\n responData.headers.get(\"content-type\") === \"application/zip\"\n ) {\n const data = await responData.blob();\n acceptUplodaChange(data);\n }\n }).finally(() => {\n setIsLoad(false)\n })\n };\n\n const renderFeatchUrlForm = (): JSX.Element => {\n return (\n
\n 返回 Blob 数据格式的接口\n setBlobUrl(e.target.value)}>\n \n
\n );\n };\n\n return (\n
\n \n \n
\n \n \n or\n \n {renderFeatchUrlForm()}\n
\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
\n );\n};\n\nexport default Page;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport \"./index.scss\";\nimport Page from \"./page\";\nimport * as serviceWorker from \"./serviceWorker\";\n\nReactDOM.render(, document.getElementById(\"root\"));\n\nserviceWorker.unregister();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /demo/build/static/js/runtime~main.909208d1.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,l=r[0],i=r[1],a=r[2],c=0,s=[];c { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /demo/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /demo/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFileName = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFileName}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /demo/config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | // We need to explicitly check for null and undefined (and not a falsy value) because 18 | // TypeScript treats an empty string as `.`. 19 | if (baseUrl == null) { 20 | // If there's no baseUrl set we respect NODE_PATH 21 | // Note that NODE_PATH is deprecated and will be removed 22 | // in the next major release of create-react-app. 23 | 24 | const nodePath = process.env.NODE_PATH || ''; 25 | return nodePath.split(path.delimiter).filter(Boolean); 26 | } 27 | 28 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 29 | 30 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 31 | // the default behavior. 32 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 33 | return null; 34 | } 35 | 36 | // Allow the user set the `baseUrl` to `appSrc`. 37 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 38 | return [paths.appSrc]; 39 | } 40 | 41 | // Otherwise, throw an error. 42 | throw new Error( 43 | chalk.red.bold( 44 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 45 | ' Create React App does not support other values at this time.' 46 | ) 47 | ); 48 | } 49 | 50 | function getModules() { 51 | // Check if TypeScript is setup 52 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 53 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 54 | 55 | if (hasTsConfig && hasJsConfig) { 56 | throw new Error( 57 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 58 | ); 59 | } 60 | 61 | let config; 62 | 63 | // If there's a tsconfig.json we assume it's a 64 | // TypeScript project and set up the config 65 | // based on tsconfig.json 66 | if (hasTsConfig) { 67 | const ts = require(resolve.sync('typescript', { 68 | basedir: paths.appNodeModules, 69 | })); 70 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 71 | // Otherwise we'll check if there is jsconfig.json 72 | // for non TS projects. 73 | } else if (hasJsConfig) { 74 | config = require(paths.appJsConfig); 75 | } 76 | 77 | config = config || {}; 78 | const options = config.compilerOptions || {}; 79 | 80 | const additionalModulePaths = getAdditionalModulePaths(options); 81 | 82 | return { 83 | additionalModulePaths: additionalModulePaths, 84 | hasTsConfig, 85 | }; 86 | } 87 | 88 | module.exports = getModules(); 89 | -------------------------------------------------------------------------------- /demo/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right