web/satellite: statically serve Papa Parse worker
Papa Parse, the library we use to parse CSV files in the satellite UI, uses a blob URL for its worker. This isn't allowed by our content security policy, so this change implements a Vite plugin that writes the worker code to a file that is statically served. Change-Id: I0ce58c37b86953a71b7433b789b72fbd8ede313d
This commit is contained in:
parent
9930b86791
commit
40d67065ba
@ -35,21 +35,26 @@ const isLoading = ref<boolean>(true);
|
||||
const isError = ref<boolean>(false);
|
||||
|
||||
onMounted(() => {
|
||||
Papa.parse(props.src, {
|
||||
download: true,
|
||||
worker: true,
|
||||
header: false,
|
||||
skipEmptyLines: true,
|
||||
complete: (results: ParseResult<string[]>) => {
|
||||
if (results) items.value = results.data;
|
||||
isLoading.value = false;
|
||||
},
|
||||
error: (error: Error) => {
|
||||
if (isError.value) return;
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
},
|
||||
});
|
||||
try {
|
||||
Papa.parse(props.src, {
|
||||
download: true,
|
||||
worker: true,
|
||||
header: false,
|
||||
skipEmptyLines: true,
|
||||
complete: (results: ParseResult<string[]>) => {
|
||||
if (results) items.value = results.data;
|
||||
isLoading.value = false;
|
||||
},
|
||||
error: (error: Error) => {
|
||||
if (isError.value) return;
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import Papa from 'papaparse';
|
||||
import PAPA_PARSE_WORKER_URL from 'virtual:papa-parse-worker';
|
||||
|
||||
import App from './App.vue';
|
||||
import { router } from './router';
|
||||
@ -66,3 +68,7 @@ app.directive('number', {
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
// By default, Papa Parse uses a blob URL for loading its worker.
|
||||
// This isn't supported by our content security policy, so we override the URL.
|
||||
Object.assign(Papa, { BLOB_URL: PAPA_PARSE_WORKER_URL });
|
||||
|
@ -8,6 +8,7 @@ import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
import vuetifyThemeCSS from './vitePlugins/vuetifyThemeCSS';
|
||||
import papaParseWorker from './vitePlugins/papaParseWorker';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@ -24,6 +25,7 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
vuetifyThemeCSS(),
|
||||
papaParseWorker(),
|
||||
],
|
||||
define: {
|
||||
'process.env': {},
|
||||
|
@ -10,6 +10,8 @@ import viteCompression from 'vite-plugin-compression';
|
||||
import vitePluginRequire from 'vite-plugin-require';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
|
||||
import papaParseWorker from './vitePlugins/papaParseWorker';
|
||||
|
||||
const productionBrotliExtensions = ['js', 'css', 'ttf', 'woff', 'woff2'];
|
||||
|
||||
const plugins = [
|
||||
@ -20,6 +22,7 @@ const plugins = [
|
||||
},
|
||||
}),
|
||||
vitePluginRequire.default(),
|
||||
papaParseWorker(),
|
||||
];
|
||||
|
||||
if (process.env['STORJ_DEBUG_BUNDLE_SIZE']) {
|
||||
|
67
web/satellite/vitePlugins/papaParseWorker/index.ts
Normal file
67
web/satellite/vitePlugins/papaParseWorker/index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { Plugin } from 'vite';
|
||||
import { build } from 'esbuild';
|
||||
|
||||
export default function papaParseWorker(): Plugin {
|
||||
const name = 'papa-parse-worker';
|
||||
const virtualModuleId = 'virtual:' + name;
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
|
||||
let refId = '';
|
||||
|
||||
return {
|
||||
name,
|
||||
|
||||
async buildStart() {
|
||||
// Trick Papa Parse into thinking it's being imported by RequireJS
|
||||
// so we can capture the AMD callback.
|
||||
let factory: (() => unknown) | undefined;
|
||||
global.define = (_: unknown, callback: () => void) => {
|
||||
factory = callback;
|
||||
};
|
||||
global.define.amd = true;
|
||||
await import('papaparse');
|
||||
delete global.define;
|
||||
|
||||
if (!factory) {
|
||||
throw new Error('Failed to capture Papa Parse AMD callback');
|
||||
}
|
||||
|
||||
const workerCode = `
|
||||
var global = (function() {
|
||||
if (typeof self !== 'undefined') { return self; }
|
||||
if (typeof window !== 'undefined') { return window; }
|
||||
if (typeof global !== 'undefined') { return global; }
|
||||
return {};
|
||||
})();
|
||||
global.IS_PAPA_WORKER = true;
|
||||
(${factory.toString()})();`;
|
||||
|
||||
const result = await build({
|
||||
stdin: {
|
||||
contents: workerCode,
|
||||
},
|
||||
write: false,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
refId = this.emitFile({
|
||||
type: 'asset',
|
||||
name: `papaparse-worker.js`,
|
||||
source: result.outputFiles[0].text,
|
||||
});
|
||||
},
|
||||
|
||||
resolveId(id: string) {
|
||||
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
||||
},
|
||||
|
||||
load(id: string) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
return `export default '__VITE_ASSET__${refId}__';`;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
7
web/satellite/vitePlugins/papaParseWorker/module.d.ts
vendored
Normal file
7
web/satellite/vitePlugins/papaParseWorker/module.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
declare module 'virtual:papa-parse-worker' {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
@ -13,7 +13,7 @@ export default function vuetifyThemeCSS(): Plugin {
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
|
||||
const theme = createVuetify({ theme: THEME_OPTIONS }).theme;
|
||||
const themeURLs: Record<string, string> = {};
|
||||
const refIds: Record<string, string> = {};
|
||||
|
||||
return {
|
||||
name,
|
||||
@ -36,7 +36,7 @@ export default function vuetifyThemeCSS(): Plugin {
|
||||
name: `theme-${name}.css`,
|
||||
source: result.outputFiles[0].text,
|
||||
});
|
||||
themeURLs[name] = `__VITE_ASSET__${refId}__`;
|
||||
refIds[name] = refId;
|
||||
}
|
||||
},
|
||||
|
||||
@ -46,9 +46,9 @@ export default function vuetifyThemeCSS(): Plugin {
|
||||
|
||||
load(id: string) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
return `export const themeURLs = {${
|
||||
Object.entries(themeURLs)
|
||||
.map(([name, url]) => `'${name}':'${url}'`)
|
||||
return `export default {${
|
||||
Object.entries(refIds)
|
||||
.map(([name, refId]) => `'${name}':'__VITE_ASSET__${refId}__'`)
|
||||
.join(',')
|
||||
}};`;
|
||||
}
|
||||
|
@ -2,5 +2,6 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
declare module 'virtual:vuetify-theme-css' {
|
||||
export const themeURLs: Record<string, string>;
|
||||
const themeURLs: Record<string, string>;
|
||||
export default themeURLs;
|
||||
}
|
||||
|
@ -38,21 +38,26 @@ const isLoading = ref<boolean>(true);
|
||||
const isError = ref<boolean>(false);
|
||||
|
||||
onMounted(() => {
|
||||
Papa.parse(props.src, {
|
||||
download: true,
|
||||
worker: true,
|
||||
header: false,
|
||||
skipEmptyLines: true,
|
||||
complete: (results: ParseResult<string[]>) => {
|
||||
if (results) items.value = results.data;
|
||||
isLoading.value = false;
|
||||
},
|
||||
error: (error: Error) => {
|
||||
if (isError.value) return;
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
},
|
||||
});
|
||||
try {
|
||||
Papa.parse(props.src, {
|
||||
download: true,
|
||||
worker: true,
|
||||
header: false,
|
||||
skipEmptyLines: true,
|
||||
complete: (results: ParseResult<string[]>) => {
|
||||
if (results) items.value = results.data;
|
||||
isLoading.value = false;
|
||||
},
|
||||
error: (error: Error) => {
|
||||
if (isError.value) return;
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
notify.error(`Error parsing object. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
|
||||
isError.value = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
*/
|
||||
// Components
|
||||
import { createApp } from 'vue';
|
||||
import Papa from 'papaparse';
|
||||
import PAPA_PARSE_WORKER_URL from 'virtual:papa-parse-worker';
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
@ -19,3 +21,7 @@ const app = createApp(App);
|
||||
registerPlugins(app);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
// By default, Papa Parse uses a blob URL for loading its worker.
|
||||
// This isn't supported by our content security policy, so we override the URL.
|
||||
Object.assign(Papa, { BLOB_URL: PAPA_PARSE_WORKER_URL });
|
||||
|
@ -10,7 +10,7 @@
|
||||
// Plugins
|
||||
import { App, watch } from 'vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { themeURLs } from 'virtual:vuetify-theme-css';
|
||||
import THEME_URLS from 'virtual:vuetify-theme-css';
|
||||
|
||||
import { router, startTitleWatcher } from '../router';
|
||||
|
||||
@ -35,7 +35,7 @@ function setupTheme() {
|
||||
|
||||
const themeLinks: Record<string, HTMLLinkElement> = {};
|
||||
|
||||
for (const [name, url] of Object.entries(themeURLs)) {
|
||||
for (const [name, url] of Object.entries(THEME_URLS)) {
|
||||
let link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = url;
|
||||
|
Loading…
Reference in New Issue
Block a user