From d4eacadb3a6931f0f454356264cc02b16f6a082f Mon Sep 17 00:00:00 2001 From: ochafik Date: Tue, 31 Dec 2024 15:54:09 +0000 Subject: [PATCH] Replace rollup w/ webpack for the web worker --- openscad-worker.rollup.config.js | 25 --- package.json | 10 +- src/fs/BrowserFS.d.ts | 32 +++- src/index.tsx | 4 +- src/runner/openscad-runner.ts | 31 ++-- src/runner/openscad-worker.ts | 26 ++- src/utils.ts | 6 +- tsconfig.json | 18 ++- webpack.config.js | 268 +++++++++++++++++++------------ 9 files changed, 232 insertions(+), 188 deletions(-) delete mode 100644 openscad-worker.rollup.config.js diff --git a/openscad-worker.rollup.config.js b/openscad-worker.rollup.config.js deleted file mode 100644 index b0a23cb..0000000 --- a/openscad-worker.rollup.config.js +++ /dev/null @@ -1,25 +0,0 @@ -import typescript from 'rollup-plugin-typescript2'; -import replace from '@rollup/plugin-replace'; -import packageConfig from './package.json' - -const LOCAL_URL = process.env.LOCAL_URL ?? 'http://localhost:4000/'; -const PUBLIC_URL = process.env.PUBLIC_URL ?? packageConfig.homepage; - -export default [ - { - input: 'src/runner/openscad-worker.ts', - output: { - file: 'dist/openscad-worker.js', - format: 'es' - }, - plugins: [ - typescript({ - rootDir: 'src', - }), - replace({ - preventAssignment: true, - 'import.meta.url': JSON.stringify(process.env.NODE_ENV !== 'production' ? LOCAL_URL : PUBLIC_URL), - }) - ] - }, -]; diff --git a/package.json b/package.json index ccf899a..09719c0 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ }, "scripts": { "test:e2e": "jest", - "start:development": "concurrently 'npx webpack serve --mode=development' 'NODE_ENV=development npx rollup --config openscad-worker.rollup.config.js --watch'", + "start:development": "npx webpack serve --mode=development", "start:production": "NODE_ENV=production PUBLIC_URL=http://localhost:3000/dist/ npm run build && npx serve", "start": "npm run start:development", - "build": "NODE_ENV=production npx rollup --config openscad-worker.rollup.config.js && webpack --mode=production" + "build": "NODE_ENV=production webpack --mode=production" }, "eslintConfig": { "extends": [ @@ -52,8 +52,6 @@ "node": ">=18.12.0" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@rollup/plugin-typescript": "^11.1.6", "@types/chroma-js": "^2.4.5", "@types/debug": "^4.1.12", "@types/filesystem": "^0.0.36", @@ -62,15 +60,13 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@types/uzip": "^0.20201231.2", - "concurrently": "^7.6.0", + "@types/web": "^0.0.140", "copy-webpack-plugin": "^11.0.0", "css-loader": "^7.1.2", "jest": "^29.7.0", "jest-puppeteer": "^11.0.0", "livereload": "^0.9.3", "puppeteer": "^23.11.1", - "rollup": "^2.79.2", - "rollup-plugin-typescript2": "^0.36.0", "serve": "^14.2.4", "style-loader": "^4.0.0", "ts-loader": "^9.5.1", diff --git a/src/fs/BrowserFS.d.ts b/src/fs/BrowserFS.d.ts index c947e3f..f6173a6 100644 --- a/src/fs/BrowserFS.d.ts +++ b/src/fs/BrowserFS.d.ts @@ -11,13 +11,29 @@ declare interface FS { declare interface EmscriptenFS extends FS {} -declare type BrowserFSInterface = { - BFSRequire: (name: string) => any, +declare interface BrowserFSInterface { + EmscriptenFS: any; + BFSRequire: (module: string) => any; + install: (obj: any) => void; + configure: (config: any, cb: (e?: Error) => void) => void; + FileSystem: { + InMemory: any; + ZipFS: any; + MountableFileSystem: any; + LocalStorage: any; + XmlHttpRequest: any; + }; + Buffer: { + from: (data: any, encoding?: string) => any; + alloc: (size: number) => any; + }; + initialize: (config: any) => Promise; + WorkerFS?: any; +} + +declare var BrowserFS: BrowserFSInterface; - install: (windowOrSelf: Window) => void, - configure: (options: any, callback: (e?: any) => void) => void, +declare module 'browserfs' { + export = BrowserFS; +} - EmscriptenFS: { - new(fs: FS, path: string, errnoCodes: object): EmscriptenFS - } -}; diff --git a/src/index.tsx b/src/index.tsx index 4456c80..5f507cc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -61,14 +61,14 @@ window.addEventListener('load', async () => { registerCustomAppHeightCSSProperty(); - const fs = await createEditorFS({prefix: '/libraries/', allowPersistence: isInStandaloneMode}); + const fs = await createEditorFS({prefix: '/libraries/', allowPersistence: isInStandaloneMode()}); await registerOpenSCADLanguage(fs, '/', zipArchives); let statePersister: StatePersister; let persistedState: State | null = null; - if (isInStandaloneMode) { + if (isInStandaloneMode()) { const fs: FS = BrowserFS.BFSRequire('fs') try { const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json'))); diff --git a/src/runner/openscad-runner.ts b/src/runner/openscad-runner.ts index 5dd976b..ba0d1f1 100644 --- a/src/runner/openscad-runner.ts +++ b/src/runner/openscad-runner.ts @@ -4,26 +4,13 @@ import { MergedOutputs } from "./openscad-worker"; import { AbortablePromise } from "../utils"; import { Source } from "../state/app-state"; -export function createWasmMemory({maximumMegabytes, maximumBytes}: {maximumMegabytes: number, maximumBytes: number}) { - const pageSize = 64 * 1024; // 64KB - if (!maximumBytes) { - maximumBytes = maximumMegabytes * 1024 * 1024; - } - return new WebAssembly.Memory({ - initial: Math.floor(maximumBytes / 2 / pageSize), - maximum: Math.floor(maximumBytes / pageSize), - shared: true, - }); -} - -// Output is {outputs: [name, content][], mergedOutputs: [{(stderr|stdout|error)?: string}], exitCode: number} export type OpenSCADInvocation = { - wasmMemory?: WebAssembly.Memory, mountArchives: boolean, inputs?: Source[], args: string[], outputPaths?: string[], } + export type OpenSCADInvocationResults = { exitCode?: number, error?: string, @@ -31,12 +18,16 @@ export type OpenSCADInvocationResults = { mergedOutputs: MergedOutputs, elapsedMillis: number, }; + export type ProcessStreams = {stderr: string} | {stdout: string} export type OpenSCADInvocationCallback = {result: OpenSCADInvocationResults} | ProcessStreams; -export function spawnOpenSCAD(invocation: OpenSCADInvocation, streamsCallback: (ps: ProcessStreams) => void): AbortablePromise { - var worker: Worker | null; - var rejection: (err: any) => void; +export function spawnOpenSCAD( + invocation: OpenSCADInvocation, + streamsCallback: (ps: ProcessStreams) => void +): AbortablePromise { + let worker: Worker | null; + let rejection: (err: any) => void; function terminate() { if (!worker) { @@ -46,10 +37,10 @@ export function spawnOpenSCAD(invocation: OpenSCADInvocation, streamsCallback: ( worker = null; } - return AbortablePromise((resolve, reject) => { - worker = new Worker('./openscad-worker.js'); + return AbortablePromise((resolve: (result: OpenSCADInvocationResults) => void, reject: (error: any) => void) => { + worker = new Worker('./openscad-worker.js');//, { type: 'module' }); rejection = reject; - worker.onmessage = (e: {data: OpenSCADInvocationCallback}) => { + worker.onmessage = (e: MessageEvent) => { if ('result' in e.data) { resolve(e.data.result); terminate(); diff --git a/src/runner/openscad-worker.ts b/src/runner/openscad-worker.ts index 158eb7f..3e80014 100644 --- a/src/runner/openscad-worker.ts +++ b/src/runner/openscad-worker.ts @@ -1,38 +1,36 @@ // Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING. +/// import OpenSCAD from "../wasm/openscad.js"; -import { createEditorFS, getParentDir, symlinkLibraries } from "../fs/filesystem"; -import { OpenSCADInvocation, OpenSCADInvocationCallback, OpenSCADInvocationResults } from "./openscad-runner"; -import { deployedArchiveNames, zipArchives } from "../fs/zip-archives"; -import { fetchSource } from "../utils.js"; -declare var BrowserFS: BrowserFSInterface +import { createEditorFS, getParentDir, symlinkLibraries } from "../fs/filesystem.ts"; +import { OpenSCADInvocation, OpenSCADInvocationCallback, OpenSCADInvocationResults } from "./openscad-runner.ts"; +import { deployedArchiveNames, zipArchives } from "../fs/zip-archives.ts"; +import { fetchSource } from "../utils.ts"; importScripts("browserfs.min.js"); +declare const self: DedicatedWorkerGlobalScope; + export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[]; function callback(payload: OpenSCADInvocationCallback) { - postMessage(payload); + self.postMessage(payload); } -addEventListener('message', async (e) => { - +self.addEventListener('message', async (e: MessageEvent) => { const { mountArchives, inputs, args, outputPaths, - wasmMemory, - } = e.data as OpenSCADInvocation; + } = e.data; const mergedOutputs: MergedOutputs = []; let instance: any; const start = performance.now(); try { instance = await OpenSCAD({ - wasmMemory, - buffer: wasmMemory && wasmMemory.buffer, noInitialRun: true, 'print': (text: string) => { console.debug('stdout: ' + text); @@ -44,9 +42,6 @@ addEventListener('message', async (e) => { callback({stderr: text}) mergedOutputs.push({ stderr: text }) }, - 'ENV': { - 'OPENSCADPATH': '/libraries', - }, }); if (mountArchives) { @@ -140,7 +135,6 @@ addEventListener('message', async (e) => { } console.debug(result); - callback({result}); } catch (e) { const end = performance.now(); diff --git a/src/utils.ts b/src/utils.ts index 4bc8a9d..8f1b6f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -138,9 +138,9 @@ export function registerCustomAppHeightCSSProperty() { } // In PWA mode, persist files in LocalStorage instead of the hash fragment. -export const isInStandaloneMode = - // true - Boolean(('standalone' in window.navigator) && (window.navigator.standalone)); +export function isInStandaloneMode() { + return Boolean(('standalone' in window.navigator) && (window.navigator.standalone)); +} export function downloadUrl(url: string, filename: string) { const link = document.createElement('a'); diff --git a/tsconfig.json b/tsconfig.json index 3b995f3..4bba2af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,9 @@ "dom", "dom.iterable", "ES2022", - "WebWorker", + "WebWorker" ], "rootDir": "src", - // "outDir": "js", "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -17,17 +16,22 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - // "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "noEmit": true, + "allowImportingTsExtensions": true, + "declaration": true, + "baseUrl": "src", + "paths": { + "*": ["*"] + } }, "include": [ "src/**/*.ts", "src/**/*.tsx", - "src/**/*.js", - // "public" + "src/**/*.js" ], "exclude": [ "node_modules", diff --git a/webpack.config.js b/webpack.config.js index 732dafa..ed5b3f9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,111 +1,179 @@ +const webpack = require('webpack'); const CopyPlugin = require("copy-webpack-plugin"); const WorkboxPlugin = require('workbox-webpack-plugin'); -const webpack = require('webpack'); - const path = require('path'); +const packageConfig = require('./package.json'); -module.exports = [{ - entry: './src/index.tsx', - // devtool: 'inline-source-map', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - { - test: /\.css$/i, - use: [ - "style-loader", - { - loader: 'css-loader', - options:{url: false}, - } - ] - }, - // { - // test: /\.(png|gif|woff|woff2|eot|ttf|svg)$/, - // loader: "url-loader?limit=100000" - // }, - ], - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - }, - output: { - filename: 'index.js', - path: path.resolve(__dirname, 'dist'), - }, - devServer: { - static: path.join(__dirname, "dist"), - compress: true, - port: 4000, - }, - plugins: [ - new webpack.EnvironmentPlugin({ - 'process.env.NODE_ENV': 'development', - }), - ...(process.env.NODE_ENV === 'production' ? [ - new WorkboxPlugin.GenerateSW({ - exclude: [ - /(^|\/)\./, - /\.map$/, - /^manifest.*\.js$/, - ], - // these options encourage the ServiceWorkers to get in there fast - // and not allow any straggling "old" SWs to hang around - swDest: path.join(__dirname, "dist", 'sw.js'), - maximumFileSizeToCacheInBytes: 200 * 1024 * 1024, - clientsClaim: true, - skipWaiting: true, - runtimeCaching: [{ - urlPattern: ({request, url}) => true, - handler: 'StaleWhileRevalidate', +const LOCAL_URL = process.env.LOCAL_URL ?? 'http://localhost:4000/'; +const PUBLIC_URL = process.env.PUBLIC_URL ?? packageConfig.homepage; +const isDev = process.env.NODE_ENV !== 'production'; + +module.exports = [ + { + entry: './src/index.tsx', + devtool: isDev ? 'source-map' : 'nosources-source-map', + mode: isDev ? 'development' : 'production', + target: 'web', + // devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: { + loader: 'ts-loader', options: { - cacheName: 'all', - expiration: { - maxEntries: 1000, - purgeOnQuotaError: true, - }, - }, - }], - }), - ] : []), - new CopyPlugin({ - patterns: [ - { - from: path.resolve(__dirname, 'public'), - toType: 'dir', + transpileOnly: true, + compilerOptions: { + module: 'esnext', + moduleResolution: 'node', + target: 'ES2022', + lib: ['WebWorker', 'ES2022'], + sourceMap: isDev, + inlineSources: isDev + } + } + }, + exclude: /node_modules/, }, - { - from: path.resolve(__dirname, 'node_modules/primeicons/fonts'), - to: path.resolve(__dirname, 'dist/fonts'), - toType: 'dir', - }, - { - from: path.resolve(__dirname, 'src/wasm/openscad.js'), - from: path.resolve(__dirname, 'src/wasm/openscad.wasm'), + { + test: /\.css$/i, + use: [ + "style-loader", + { + loader: 'css-loader', + options:{url: false}, + } + ] }, + // { + // test: /\.(png|gif|woff|woff2|eot|ttf|svg)$/, + // loader: "url-loader?limit=100000" + // }, ], - }), - ], -}/*, { - entry: './src/sw.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: 'index.js', + path: path.resolve(__dirname, 'dist'), + }, + devServer: { + static: path.join(__dirname, "dist"), + compress: true, + port: 4000, + }, + plugins: [ + new webpack.EnvironmentPlugin({ + 'process.env.NODE_ENV': 'development', + }), + ...(process.env.NODE_ENV === 'production' ? [ + new WorkboxPlugin.GenerateSW({ + exclude: [ + /(^|\/)\./, + /\.map$/, + /^manifest.*\.js$/, + ], + // these options encourage the ServiceWorkers to get in there fast + // and not allow any straggling "old" SWs to hang around + swDest: path.join(__dirname, "dist", 'sw.js'), + maximumFileSizeToCacheInBytes: 200 * 1024 * 1024, + clientsClaim: true, + skipWaiting: true, + runtimeCaching: [{ + urlPattern: ({request, url}) => true, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'all', + expiration: { + maxEntries: 1000, + purgeOnQuotaError: true, + }, + }, + }], + }), + ] : []), + new CopyPlugin({ + patterns: [ + { + from: path.resolve(__dirname, 'public'), + toType: 'dir', + }, + { + from: path.resolve(__dirname, 'node_modules/primeicons/fonts'), + to: path.resolve(__dirname, 'dist/fonts'), + toType: 'dir', + }, + { + from: path.resolve(__dirname, 'src/wasm/openscad.js'), + from: path.resolve(__dirname, 'src/wasm/openscad.wasm'), + }, + ], + }), ], }, - resolve: { - extensions: ['.ts', '.js'], - }, - output: { - filename: 'sw.js', - path: path.resolve(__dirname, 'dist'), + { + entry: './src/runner/openscad-worker.ts', + output: { + filename: 'openscad-worker.js', + path: path.resolve(__dirname, 'dist'), + globalObject: 'self', + // library: { + // type: 'module' + // } + }, + devtool: isDev ? 'source-map' : 'nosources-source-map', + mode: 'production', + // mode: isDev ? 'development' : 'production', + target: 'webworker', + // experiments: { + // outputModule: true, + // }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: { + loader: 'ts-loader', + options: { + transpileOnly: true, + compilerOptions: { + module: 'esnext', + moduleResolution: 'node', + target: 'ES2022', + lib: ['WebWorker', 'ES2022'], + sourceMap: isDev, + inlineSources: isDev + } + } + }, + exclude: /node_modules/, + }, + { + test: /\.wasm$/, + type: 'asset/resource' + } + ] + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.mjs', '.wasm'], + modules: [ + path.resolve(__dirname, 'src'), + 'node_modules' + ], + fallback: { + fs: false, + path: false, + module: false + } + }, + externals: { + 'browserfs': 'BrowserFS' + }, + plugins: [ + new webpack.EnvironmentPlugin({ + 'process.env.NODE_ENV': 'development', + }), + ], }, -}*/]; +];