using webpack #122
This commit is contained in:
parent
b8d8f545b0
commit
52930445fc
7 changed files with 128 additions and 120 deletions
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
|
|
@ -25,8 +25,7 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node.version }}
|
||||
- run: npm install
|
||||
- run: make public
|
||||
- run: npm run build
|
||||
- run: npm run build:all
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ Licenses: see [LICENSES](./LICENSE).
|
|||
|
||||
## Building
|
||||
|
||||
The project uses a **webpack-based build system** that reads library metadata from `libs-config.json` to automatically download, clone, and package OpenSCAD libraries and dependencies. This replaces the previous Makefile approach with a more standard, maintainable solution.
|
||||
|
||||
Prerequisites:
|
||||
* wget or curl
|
||||
* Node.js (>=18.12.0)
|
||||
|
|
@ -90,7 +92,7 @@ rm -fR ../ochafik.github.io/openscad2 && cp -R dist ../ochafik.github.io/opensca
|
|||
|
||||
## Build your own WASM binary
|
||||
|
||||
[Makefile](./Makefile) fetches a prebuilt OpenSCAD web WASM binary, but you can build your own in a couple of minutes:
|
||||
The build system fetches a prebuilt OpenSCAD web WASM binary, but you can build your own in a couple of minutes:
|
||||
|
||||
- **Optional**: use your own openscad fork / branch:
|
||||
|
||||
|
|
@ -117,7 +119,7 @@ rm -fR ../ochafik.github.io/openscad2 && cp -R dist ../ochafik.github.io/opensca
|
|||
|
||||
## Adding OpenSCAD libraries
|
||||
|
||||
You'll need to update 3 files (search for BOSL2 for an example):
|
||||
The build system uses a webpack plugin that reads from `libs-config.json` to manage all library dependencies. You'll need to update 3 files (search for BOSL2 for an example):
|
||||
|
||||
- [libs-config.json](./libs-config.json): to add the library's metadata including repository URL, branch, and files to include/exclude in the zip archive
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const config = {
|
|||
],
|
||||
},
|
||||
server: {
|
||||
command: `npm run start:${process.env.NODE_ENV}`,
|
||||
command: `npm run start:${process.env.NODE_ENV || 'test'}`,
|
||||
port: process.env.NODE_ENV === 'production' ? 3000 : 4000,
|
||||
launchTimeout: 180000,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,12 +30,13 @@
|
|||
"test:e2e": "jest",
|
||||
"start:development": "npx webpack serve --mode=development",
|
||||
"start:production": "NODE_ENV=production PUBLIC_URL=http://localhost:3000/dist/ npm run build && npx serve",
|
||||
"start:test": "npm run start:development",
|
||||
"start": "npm run start:development",
|
||||
"build": "NODE_ENV=production webpack --mode=production",
|
||||
"build:libs": "node build-libs.js build",
|
||||
"build:libs:clean": "node build-libs.js clean",
|
||||
"build:libs:wasm": "node build-libs.js wasm",
|
||||
"build:libs:fonts": "node build-libs.js fonts",
|
||||
"build:libs": "LIBS_BUILD_MODE=all webpack --config webpack.libs.config.js",
|
||||
"build:libs:clean": "LIBS_BUILD_MODE=clean webpack --config webpack.libs.config.js",
|
||||
"build:libs:wasm": "LIBS_BUILD_MODE=wasm webpack --config webpack.libs.config.js",
|
||||
"build:libs:fonts": "LIBS_BUILD_MODE=fonts webpack --config webpack.libs.config.js",
|
||||
"build:all": "npm run build:libs && npm run build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
|
|
|||
150
build-libs.js → webpack-libs-plugin.js
Executable file → Normal file
150
build-libs.js → webpack-libs-plugin.js
Executable file → Normal file
|
|
@ -1,32 +1,63 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import { createWriteStream, existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import https from 'https';
|
||||
import { pipeline } from 'stream/promises';
|
||||
import { exec } from 'node:child_process';
|
||||
import { createWriteStream, existsSync } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import https from 'node:https';
|
||||
import path from 'node:path';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const CONFIG_FILE = 'libs-config.json';
|
||||
const LIBS_DIR = 'libs';
|
||||
const PUBLIC_LIBS_DIR = 'public/libraries';
|
||||
const SRC_WASM_DIR = 'src/wasm';
|
||||
|
||||
class LibsBuilder {
|
||||
constructor() {
|
||||
class OpenSCADLibrariesPlugin {
|
||||
constructor(options = {}) {
|
||||
this.configFile = options.configFile || 'libs-config.json';
|
||||
this.libsDir = options.libsDir || 'libs';
|
||||
this.publicLibsDir = options.publicLibsDir || 'public/libraries';
|
||||
this.srcWasmDir = options.srcWasmDir || 'src/wasm';
|
||||
this.buildMode = options.buildMode || 'all'; // 'all', 'wasm', 'fonts', 'libs'
|
||||
this.config = null;
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
const pluginName = 'OpenSCADLibrariesPlugin';
|
||||
|
||||
compiler.hooks.beforeRun.tapAsync(pluginName, async (_, callback) => {
|
||||
try {
|
||||
await this.loadConfig();
|
||||
|
||||
switch (this.buildMode) {
|
||||
case 'all':
|
||||
await this.buildAll();
|
||||
break;
|
||||
case 'wasm':
|
||||
await this.buildWasm();
|
||||
break;
|
||||
case 'fonts':
|
||||
await this.buildFonts();
|
||||
break;
|
||||
case 'libs':
|
||||
await this.buildAllLibraries();
|
||||
break;
|
||||
case 'clean':
|
||||
await this.clean();
|
||||
break;
|
||||
}
|
||||
|
||||
callback();
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
try {
|
||||
const configContent = await fs.readFile(CONFIG_FILE, 'utf-8');
|
||||
const configContent = await fs.readFile(this.configFile, 'utf-8');
|
||||
this.config = JSON.parse(configContent);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load config from ${CONFIG_FILE}:`, error.message);
|
||||
process.exit(1);
|
||||
throw new Error(`Failed to load config from ${this.configFile}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +77,6 @@ class LibsBuilder {
|
|||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (response) => {
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
// Handle redirects
|
||||
return this.downloadFile(response.headers.location, outputPath)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
|
|
@ -95,23 +125,18 @@ class LibsBuilder {
|
|||
if (includes.length > 0) {
|
||||
const findPatterns = includes.map(pattern => {
|
||||
if (pattern.includes('**/*.')) {
|
||||
// Pattern like "examples/**/*.scad"
|
||||
const parts = pattern.split('/');
|
||||
const dir = parts[0];
|
||||
const filePattern = parts[parts.length - 1];
|
||||
return `-path "./${dir}/*" -name "${filePattern}"`;
|
||||
} else if (pattern.includes('**')) {
|
||||
// Pattern like "**/*.scad"
|
||||
const filePattern = pattern.replace('**/', '');
|
||||
return `-name "${filePattern}"`;
|
||||
} else if (pattern.includes('*')) {
|
||||
// Pattern like "*.scad"
|
||||
return `-name "${pattern}"`;
|
||||
} else if (pattern.includes('/')) {
|
||||
// Path pattern like "bitmap/*.scad"
|
||||
return `-path "./${pattern}"`;
|
||||
} else {
|
||||
// Direct file/directory name
|
||||
return `-name "${pattern}" -o -path "./${pattern}/*"`;
|
||||
}
|
||||
}).join(' -o ');
|
||||
|
|
@ -132,7 +157,6 @@ class LibsBuilder {
|
|||
const zipCmd = `cd ${fullSourceDir} && ${findCmd} | zip -r ${path.resolve(outputPath)} -@`;
|
||||
|
||||
console.log(`Creating zip: ${outputPath}`);
|
||||
console.log(`Zip command: ${zipCmd}`);
|
||||
try {
|
||||
await execAsync(zipCmd);
|
||||
} catch (error) {
|
||||
|
|
@ -146,22 +170,16 @@ class LibsBuilder {
|
|||
const wasmDir = wasmBuild.target;
|
||||
const wasmZip = `${wasmDir}.zip`;
|
||||
|
||||
// Create libs directory
|
||||
await this.ensureDir(LIBS_DIR);
|
||||
await this.ensureDir(this.libsDir);
|
||||
|
||||
// Download WASM if not exists
|
||||
if (!existsSync(wasmDir)) {
|
||||
await this.ensureDir(wasmDir);
|
||||
|
||||
// Download WASM zip
|
||||
await this.downloadFile(wasmBuild.url, wasmZip);
|
||||
|
||||
// Extract WASM zip
|
||||
console.log(`Extracting WASM to ${wasmDir}`);
|
||||
await execAsync(`cd ${wasmDir} && unzip ../${path.basename(wasmZip)}`);
|
||||
}
|
||||
|
||||
// Create symlinks for public files
|
||||
await this.ensureDir('public');
|
||||
|
||||
const jsTarget = 'public/openscad.js';
|
||||
|
|
@ -170,34 +188,28 @@ class LibsBuilder {
|
|||
// Remove existing symlinks/files
|
||||
try {
|
||||
await fs.unlink(jsTarget);
|
||||
} catch {
|
||||
// ignore - file doesn't exist
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
try {
|
||||
await fs.unlink(wasmTarget);
|
||||
} catch {
|
||||
// ignore - file doesn't exist
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Create new symlinks - use relative paths for portability
|
||||
// Create new symlinks
|
||||
await fs.symlink(path.relative('public', path.join(wasmDir, 'openscad.js')), jsTarget);
|
||||
await fs.symlink(path.relative('public', path.join(wasmDir, 'openscad.wasm')), wasmTarget);
|
||||
|
||||
// Create src/wasm symlink
|
||||
try {
|
||||
await fs.unlink(SRC_WASM_DIR);
|
||||
} catch {
|
||||
// ignore - file doesn't exist
|
||||
}
|
||||
await fs.symlink(path.relative('src', wasmDir), SRC_WASM_DIR);
|
||||
await fs.unlink(this.srcWasmDir);
|
||||
} catch { /* ignore */ }
|
||||
await fs.symlink(path.relative('src', wasmDir), this.srcWasmDir);
|
||||
|
||||
console.log('WASM setup completed');
|
||||
}
|
||||
|
||||
async buildFonts() {
|
||||
const { fonts } = this.config;
|
||||
const notoDir = path.join(LIBS_DIR, 'noto');
|
||||
const liberationDir = path.join(LIBS_DIR, 'liberation');
|
||||
const notoDir = path.join(this.libsDir, 'noto');
|
||||
const liberationDir = path.join(this.libsDir, 'liberation');
|
||||
|
||||
await this.ensureDir(notoDir);
|
||||
|
||||
|
|
@ -216,8 +228,8 @@ class LibsBuilder {
|
|||
}
|
||||
|
||||
// Create fonts zip
|
||||
const fontsZip = path.join(PUBLIC_LIBS_DIR, 'fonts.zip');
|
||||
await this.ensureDir(PUBLIC_LIBS_DIR);
|
||||
const fontsZip = path.join(this.publicLibsDir, 'fonts.zip');
|
||||
await this.ensureDir(this.publicLibsDir);
|
||||
|
||||
console.log('Creating fonts.zip');
|
||||
const fontsCmd = `zip -r ${fontsZip} -j fonts.conf libs/noto/*.ttf libs/liberation/*.ttf libs/liberation/LICENSE libs/liberation/AUTHORS`;
|
||||
|
|
@ -227,8 +239,8 @@ class LibsBuilder {
|
|||
}
|
||||
|
||||
async buildLibrary(library) {
|
||||
const libDir = path.join(LIBS_DIR, library.name);
|
||||
const zipPath = path.join(PUBLIC_LIBS_DIR, `${library.name}.zip`);
|
||||
const libDir = path.join(this.libsDir, library.name);
|
||||
const zipPath = path.join(this.publicLibsDir, `${library.name}.zip`);
|
||||
|
||||
// Clone repository if not exists
|
||||
if (!existsSync(libDir)) {
|
||||
|
|
@ -248,7 +260,7 @@ class LibsBuilder {
|
|||
}
|
||||
|
||||
async buildAllLibraries() {
|
||||
await this.ensureDir(PUBLIC_LIBS_DIR);
|
||||
await this.ensureDir(this.publicLibsDir);
|
||||
|
||||
for (const library of this.config.libraries) {
|
||||
await this.buildLibrary(library);
|
||||
|
|
@ -259,12 +271,12 @@ class LibsBuilder {
|
|||
console.log('Cleaning build artifacts...');
|
||||
|
||||
const cleanPaths = [
|
||||
LIBS_DIR,
|
||||
this.libsDir,
|
||||
'build',
|
||||
'public/openscad.js',
|
||||
'public/openscad.wasm',
|
||||
`${PUBLIC_LIBS_DIR}/*.zip`,
|
||||
SRC_WASM_DIR
|
||||
`${this.publicLibsDir}/*.zip`,
|
||||
this.srcWasmDir
|
||||
];
|
||||
|
||||
for (const cleanPath of cleanPaths) {
|
||||
|
|
@ -282,7 +294,7 @@ class LibsBuilder {
|
|||
console.log('Clean completed');
|
||||
}
|
||||
|
||||
async build() {
|
||||
async buildAll() {
|
||||
console.log('Building all libraries...');
|
||||
|
||||
await this.buildWasm();
|
||||
|
|
@ -293,32 +305,4 @@ class LibsBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const builder = new LibsBuilder();
|
||||
await builder.loadConfig();
|
||||
|
||||
const command = process.argv[2] || 'build';
|
||||
|
||||
switch (command) {
|
||||
case 'build':
|
||||
await builder.build();
|
||||
break;
|
||||
case 'clean':
|
||||
await builder.clean();
|
||||
break;
|
||||
case 'wasm':
|
||||
await builder.buildWasm();
|
||||
break;
|
||||
case 'fonts':
|
||||
await builder.buildFonts();
|
||||
break;
|
||||
default:
|
||||
console.log('Usage: node build-libs.js [build|clean|wasm|fonts]');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Build failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
export default OpenSCADLibrariesPlugin;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import WorkboxPlugin from 'workbox-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import packageConfig from './package.json' with {type: 'json'};
|
||||
import WorkboxPlugin from 'workbox-webpack-plugin';
|
||||
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
|
@ -9,8 +8,6 @@ import {fileURLToPath} from 'url';
|
|||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
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';
|
||||
|
||||
|
||||
|
|
@ -113,7 +110,11 @@ const config = [
|
|||
},
|
||||
{
|
||||
from: path.resolve(__dirname, 'src/wasm/openscad.js'),
|
||||
to: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, 'src/wasm/openscad.wasm'),
|
||||
to: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
|
|||
21
webpack.libs.config.js
Normal file
21
webpack.libs.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import OpenSCADLibrariesPlugin from './webpack-libs-plugin.js';
|
||||
|
||||
const buildMode = process.env.LIBS_BUILD_MODE || 'all';
|
||||
|
||||
/** @type {import('webpack').Configuration} */
|
||||
const config = {
|
||||
mode: 'none', // We're not actually building JS, just using webpack as a task runner
|
||||
entry: './package.json', // Dummy entry point that exists
|
||||
output: {
|
||||
path: '/tmp', // Output to temp directory
|
||||
filename: 'webpack-libs-temp.js', // This won't be used
|
||||
},
|
||||
plugins: [
|
||||
new OpenSCADLibrariesPlugin({
|
||||
buildMode: buildMode
|
||||
}),
|
||||
],
|
||||
stats: 'minimal',
|
||||
};
|
||||
|
||||
export default config;
|
||||
Loading…
Reference in a new issue