Replace rollup w/ webpack for the web worker

This commit is contained in:
ochafik 2024-12-31 15:54:09 +00:00 • committed by Olivier Chafik
commit a0015af28e
9 changed files with 232 additions and 188 deletions

View file

@ -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),
})
]
},
];

View file

@ -25,10 +25,10 @@
}, },
"scripts": { "scripts": {
"test:e2e": "jest", "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:production": "NODE_ENV=production PUBLIC_URL=http://localhost:3000/dist/ npm run build && npx serve",
"start": "npm run start:development", "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": { "eslintConfig": {
"extends": [ "extends": [
@ -52,8 +52,6 @@
"node": ">=18.12.0" "node": ">=18.12.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-typescript": "^11.1.6",
"@types/chroma-js": "^2.4.5", "@types/chroma-js": "^2.4.5",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/filesystem": "^0.0.36", "@types/filesystem": "^0.0.36",
@ -62,15 +60,13 @@
"@types/react": "^18.3.18", "@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",
"@types/uzip": "^0.20201231.2", "@types/uzip": "^0.20201231.2",
"concurrently": "^7.6.0", "@types/web": "^0.0.140",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-puppeteer": "^11.0.0", "jest-puppeteer": "^11.0.0",
"livereload": "^0.9.3", "livereload": "^0.9.3",
"puppeteer": "^23.11.1", "puppeteer": "^23.11.1",
"rollup": "^2.79.2",
"rollup-plugin-typescript2": "^0.36.0",
"serve": "^14.2.4", "serve": "^14.2.4",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",

34
src/fs/BrowserFS.d.ts vendored
View file

@ -11,13 +11,29 @@ declare interface FS {
declare interface EmscriptenFS extends FS {} declare interface EmscriptenFS extends FS {}
declare type BrowserFSInterface = { declare interface BrowserFSInterface {
BFSRequire: (name: string) => any, EmscriptenFS: any;
BFSRequire: (module: string) => any;
install: (windowOrSelf: Window) => void, install: (obj: any) => void;
configure: (options: any, callback: (e?: any) => void) => void, configure: (config: any, cb: (e?: Error) => void) => void;
FileSystem: {
EmscriptenFS: { InMemory: any;
new(fs: FS, path: string, errnoCodes: object): EmscriptenFS ZipFS: any;
} MountableFileSystem: any;
LocalStorage: any;
XmlHttpRequest: any;
}; };
Buffer: {
from: (data: any, encoding?: string) => any;
alloc: (size: number) => any;
};
initialize: (config: any) => Promise<void>;
WorkerFS?: any;
}
declare var BrowserFS: BrowserFSInterface;
declare module 'browserfs' {
export = BrowserFS;
}

View file

@ -61,14 +61,14 @@ window.addEventListener('load', async () => {
registerCustomAppHeightCSSProperty(); registerCustomAppHeightCSSProperty();
const fs = await createEditorFS({prefix: '/libraries/', allowPersistence: isInStandaloneMode}); const fs = await createEditorFS({prefix: '/libraries/', allowPersistence: isInStandaloneMode()});
await registerOpenSCADLanguage(fs, '/', zipArchives); await registerOpenSCADLanguage(fs, '/', zipArchives);
let statePersister: StatePersister; let statePersister: StatePersister;
let persistedState: State | null = null; let persistedState: State | null = null;
if (isInStandaloneMode) { if (isInStandaloneMode()) {
const fs: FS = BrowserFS.BFSRequire('fs') const fs: FS = BrowserFS.BFSRequire('fs')
try { try {
const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json'))); const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json')));

View file

@ -4,26 +4,13 @@ import { MergedOutputs } from "./openscad-worker";
import { AbortablePromise } from "../utils"; import { AbortablePromise } from "../utils";
import { Source } from "../state/app-state"; 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 = { export type OpenSCADInvocation = {
wasmMemory?: WebAssembly.Memory,
mountArchives: boolean, mountArchives: boolean,
inputs?: Source[], inputs?: Source[],
args: string[], args: string[],
outputPaths?: string[], outputPaths?: string[],
} }
export type OpenSCADInvocationResults = { export type OpenSCADInvocationResults = {
exitCode?: number, exitCode?: number,
error?: string, error?: string,
@ -31,12 +18,16 @@ export type OpenSCADInvocationResults = {
mergedOutputs: MergedOutputs, mergedOutputs: MergedOutputs,
elapsedMillis: number, elapsedMillis: number,
}; };
export type ProcessStreams = {stderr: string} | {stdout: string} export type ProcessStreams = {stderr: string} | {stdout: string}
export type OpenSCADInvocationCallback = {result: OpenSCADInvocationResults} | ProcessStreams; export type OpenSCADInvocationCallback = {result: OpenSCADInvocationResults} | ProcessStreams;
export function spawnOpenSCAD(invocation: OpenSCADInvocation, streamsCallback: (ps: ProcessStreams) => void): AbortablePromise<OpenSCADInvocationResults> { export function spawnOpenSCAD(
var worker: Worker | null; invocation: OpenSCADInvocation,
var rejection: (err: any) => void; streamsCallback: (ps: ProcessStreams) => void
): AbortablePromise<OpenSCADInvocationResults> {
let worker: Worker | null;
let rejection: (err: any) => void;
function terminate() { function terminate() {
if (!worker) { if (!worker) {
@ -46,10 +37,10 @@ export function spawnOpenSCAD(invocation: OpenSCADInvocation, streamsCallback: (
worker = null; worker = null;
} }
return AbortablePromise<OpenSCADInvocationResults>((resolve, reject) => { return AbortablePromise<OpenSCADInvocationResults>((resolve: (result: OpenSCADInvocationResults) => void, reject: (error: any) => void) => {
worker = new Worker('./openscad-worker.js'); worker = new Worker('./openscad-worker.js');//, { type: 'module' });
rejection = reject; rejection = reject;
worker.onmessage = (e: {data: OpenSCADInvocationCallback}) => { worker.onmessage = (e: MessageEvent<OpenSCADInvocationCallback>) => {
if ('result' in e.data) { if ('result' in e.data) {
resolve(e.data.result); resolve(e.data.result);
terminate(); terminate();

View file

@ -1,38 +1,36 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING. // Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
/// <reference lib="webworker" />
import OpenSCAD from "../wasm/openscad.js"; import OpenSCAD from "../wasm/openscad.js";
import { createEditorFS, getParentDir, symlinkLibraries } from "../fs/filesystem"; import { createEditorFS, getParentDir, symlinkLibraries } from "../fs/filesystem.ts";
import { OpenSCADInvocation, OpenSCADInvocationCallback, OpenSCADInvocationResults } from "./openscad-runner"; import { OpenSCADInvocation, OpenSCADInvocationCallback, OpenSCADInvocationResults } from "./openscad-runner.ts";
import { deployedArchiveNames, zipArchives } from "../fs/zip-archives"; import { deployedArchiveNames, zipArchives } from "../fs/zip-archives.ts";
import { fetchSource } from "../utils.js"; import { fetchSource } from "../utils.ts";
declare var BrowserFS: BrowserFSInterface
importScripts("browserfs.min.js"); importScripts("browserfs.min.js");
declare const self: DedicatedWorkerGlobalScope;
export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[]; export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[];
function callback(payload: OpenSCADInvocationCallback) { function callback(payload: OpenSCADInvocationCallback) {
postMessage(payload); self.postMessage(payload);
} }
addEventListener('message', async (e) => { self.addEventListener('message', async (e: MessageEvent<OpenSCADInvocation>) => {
const { const {
mountArchives, mountArchives,
inputs, inputs,
args, args,
outputPaths, outputPaths,
wasmMemory, } = e.data;
} = e.data as OpenSCADInvocation;
const mergedOutputs: MergedOutputs = []; const mergedOutputs: MergedOutputs = [];
let instance: any; let instance: any;
const start = performance.now(); const start = performance.now();
try { try {
instance = await OpenSCAD({ instance = await OpenSCAD({
wasmMemory,
buffer: wasmMemory && wasmMemory.buffer,
noInitialRun: true, noInitialRun: true,
'print': (text: string) => { 'print': (text: string) => {
console.debug('stdout: ' + text); console.debug('stdout: ' + text);
@ -44,9 +42,6 @@ addEventListener('message', async (e) => {
callback({stderr: text}) callback({stderr: text})
mergedOutputs.push({ stderr: text }) mergedOutputs.push({ stderr: text })
}, },
'ENV': {
'OPENSCADPATH': '/libraries',
},
}); });
if (mountArchives) { if (mountArchives) {
@ -140,7 +135,6 @@ addEventListener('message', async (e) => {
} }
console.debug(result); console.debug(result);
callback({result}); callback({result});
} catch (e) { } catch (e) {
const end = performance.now(); const end = performance.now();

View file

@ -138,9 +138,9 @@ export function registerCustomAppHeightCSSProperty() {
} }
// In PWA mode, persist files in LocalStorage instead of the hash fragment. // In PWA mode, persist files in LocalStorage instead of the hash fragment.
export const isInStandaloneMode = export function isInStandaloneMode() {
// true return Boolean(('standalone' in window.navigator) && (window.navigator.standalone));
Boolean(('standalone' in window.navigator) && (window.navigator.standalone)); }
export function downloadUrl(url: string, filename: string) { export function downloadUrl(url: string, filename: string) {
const link = document.createElement('a'); const link = document.createElement('a');

View file

@ -5,10 +5,9 @@
"dom", "dom",
"dom.iterable", "dom.iterable",
"ES2022", "ES2022",
"WebWorker", "WebWorker"
], ],
"rootDir": "src", "rootDir": "src",
// "outDir": "js",
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -17,17 +16,22 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
// "noEmit": true, "jsx": "react-jsx",
"jsx": "react-jsx" "noEmit": true,
"allowImportingTsExtensions": true,
"declaration": true,
"baseUrl": "src",
"paths": {
"*": ["*"]
}
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.js", "src/**/*.js"
// "public"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View file

@ -1,17 +1,38 @@
const webpack = require('webpack');
const CopyPlugin = require("copy-webpack-plugin"); const CopyPlugin = require("copy-webpack-plugin");
const WorkboxPlugin = require('workbox-webpack-plugin'); const WorkboxPlugin = require('workbox-webpack-plugin');
const webpack = require('webpack');
const path = require('path'); const path = require('path');
const packageConfig = require('./package.json');
module.exports = [{ 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', entry: './src/index.tsx',
devtool: isDev ? 'source-map' : 'nosources-source-map',
mode: isDev ? 'development' : 'production',
target: 'web',
// devtool: 'inline-source-map', // devtool: 'inline-source-map',
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
compilerOptions: {
module: 'esnext',
moduleResolution: 'node',
target: 'ES2022',
lib: ['WebWorker', 'ES2022'],
sourceMap: isDev,
inlineSources: isDev
}
}
},
exclude: /node_modules/, exclude: /node_modules/,
}, },
{ {
@ -90,22 +111,69 @@ module.exports = [{
], ],
}), }),
], ],
}/*, { },
entry: './src/sw.ts', {
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: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
compilerOptions: {
module: 'esnext',
moduleResolution: 'node',
target: 'ES2022',
lib: ['WebWorker', 'ES2022'],
sourceMap: isDev,
inlineSources: isDev
}
}
},
exclude: /node_modules/, exclude: /node_modules/,
}, },
], {
test: /\.wasm$/,
type: 'asset/resource'
}
]
}, },
resolve: { resolve: {
extensions: ['.ts', '.js'], extensions: ['.tsx', '.ts', '.js', '.mjs', '.wasm'],
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
fallback: {
fs: false,
path: false,
module: false
}
}, },
output: { externals: {
filename: 'sw.js', 'browserfs': 'BrowserFS'
path: path.resolve(__dirname, 'dist'),
}, },
}*/]; plugins: [
new webpack.EnvironmentPlugin({
'process.env.NODE_ENV': 'development',
}),
],
},
];