oxjs/rollup.config.js

261 lines
8.5 KiB
JavaScript
Raw Permalink Normal View History

2026-02-18 17:02:08 +01:00
import resolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
import copy from 'rollup-plugin-copy';
import fs from 'fs';
import path from 'path';
import { version } from './package.json';
function install(options = {}) {
const sourcePath = options.sourcePath || 'source/';
const devPath = options.devPath || 'dev/';
const minPath = options.minPath || 'min/';
// Helper: parse CSS with variable substitutions
function parseCss(css, values) {
return css.replace(/\$(\w+)(\[\d+\])?/g, (match, key, index) => {
let value = values[key];
if (index) {
const idx = parseInt(index.slice(1, -1));
value = value[idx];
}
if (typeof value === 'string') {
return value;
}
// Handle numeric arrays (e.g., RGB or RGBA)
if (Array.isArray(value[0])) {
// Already nested arrays
return value
.map(vals => `rgb${vals.length === 4 ? 'a' : ''}(${vals.join(', ')})`)
.join(', ');
} else {
// Single array
return `rgb${value.length === 4 ? 'a' : ''}(${value.join(', ')})`;
}
});
}
function readJsonc(filePath) {
const jsoncText = fs.readFileSync(filePath, 'utf-8');
let text = jsoncText.replace(/\/\/.*$/gm, ''); // Remove single-line comments
text = text.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
text = text.replace(/,\s*(?=[}\]])/g, ''); // Remove trailing commas in objects and arrays
return JSON.parse(text);
}
function writeFile(filePath, data) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, typeof data === 'string' ? data : data.toString('utf-8'));
return data.length;
}
function formatColor(rgb) {
return '#' + rgb.map(c => c.toString(16).padStart(2, '0').toUpperCase()).join('');
}
function writeBundle() {
const themesDir = path.join(sourcePath, 'UI', 'themes');
const themes = fs.readdirSync(themesDir).filter(name => !['.', '_'].includes(name[0]));
const themeData = {};
for (const theme of themes) {
const themeJsonPath = path.join(themesDir, theme, 'json', 'theme.jsonc');
themeData[theme] = readJsonc(themeJsonPath);
themeData[theme].themeClass = 'OxTheme' + theme[0].toUpperCase() + theme.slice(1);
}
const cssPath = path.join(sourcePath, 'UI', 'css', 'theme.css');
const css = fs.readFileSync(cssPath, 'utf-8')
for (const theme of themes) {
let themeCss = parseCss(css, themeData[theme]);
themeCss = themeCss.replace(/\.png\)/g, `.png?${version})`);
writeFile(path.join(devPath, 'UI', 'themes', theme, 'css', 'theme.css'), themeCss);
writeFile(path.join(minPath, 'UI', 'themes', theme, 'css', 'theme.css'), themeCss);
}
const uiImages = {}
const svgDir = path.join(sourcePath, 'UI', 'svg');
const svgs = fs.readdirSync(svgDir).filter(name => !['.', '_'].includes(name[0]));
for (const filename of svgs) {
const svgPath = path.join(svgDir, filename);
let svg = fs.readFileSync(svgPath, 'utf-8')
svg = svg.replace(/\n\s*/g, '');
svg = svg.replace(/<!--.+?-->/g, '');
uiImages[filename.slice(0, -4)] = svg
if (filename.startsWith('symbolLoading')) {
for (const theme of themes) {
let themeSVG = svg.replace(/#808080/g, formatColor(themeData[theme]['symbolDefaultColor']))
writeFile(path.join(devPath, 'UI', 'themes', theme, 'svg', filename), themeSVG);
writeFile(path.join(minPath, 'UI', 'themes', theme, 'svg', filename), themeSVG);
}
}
}
}
return {
2026-02-18 20:19:59 +01:00
name: 'install-plugin',
2026-02-18 17:02:08 +01:00
writeBundle
};
}
2026-02-18 20:19:59 +01:00
function symlinkDevPlugin(options = {}) {
const sourcePath = options.source || 'source';
const devPath = options.dev || 'dev';
function shouldInclude(filePath, filename) {
if (filePath.includes('_')) return false;
if (filename.startsWith('.') || filename.startsWith('_')) return false;
if (filename.endsWith('~')) return false;
if (filePath.includes(`${path.sep}UI${path.sep}svg`)) return false;
return true;
}
function ensureDir(dir) {
fs.mkdirSync(dir, { recursive: true });
}
function removeIfExists(target) {
if (fs.existsSync(target) || fs.lstatSync(target, { throwIfNoEntry: false })) {
try {
const stat = fs.lstatSync(target);
if (stat.isSymbolicLink()) {
fs.unlinkSync(target);
} else if (stat.isDirectory()) {
fs.rmdirSync(target);
} else {
console.log("not symlink, what to do?", target)
}
} catch (err) {
// ignore if it doesn't exist anymore
}
}
}
function walk(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(fullPath);
} else if (entry.isFile()) {
if (!shouldInclude(dir, entry.name)) continue;
const relativePath = path.relative(sourcePath, dir);
const targetDir = path.join(devPath, relativePath);
const targetPath = path.join(targetDir, entry.name);
const relativeSource = path.relative(targetDir, fullPath);
ensureDir(targetDir);
removeIfExists(targetPath);
if (!fs.existsSync(targetPath)) {
fs.symlinkSync(relativeSource, targetPath);
}
}
}
}
return {
name: 'symlink-dev-plugin',
writeBundle() {
if (!fs.existsSync(sourcePath)) {
this.warn(`Source path "${sourcePath}" does not exist.`);
return;
}
walk(sourcePath);
}
};
}
2026-02-18 17:02:08 +01:00
// TBD: get version
// TBD: add ' OxJS %s (c) %s 0x2620, dual-licensed GPL/MIT, see https://oxjs.org for details ' % (version, year)
//
/*
// kind of inline now, but missing cache busting!
# Ox.UI CSS
css = read_text(source_path + 'UI/css/UI.css')
css = css.replace('$import', '\n'.join([
'@import url("../themes/%s/css/theme.css?%s");' % (theme, version) for theme in themes
]))
write_file('%sUI/css/UI.css' % dev_path, css)
write_file('%sUI/css/UI.css' % min_path, css)
# Ox.UI SVGs
ui_images = {}
path = source_path + 'UI/svg/'
for filename in [filename for filename in os.listdir(path) if not filename[0] in '._']:
svg = read_text(path + filename)
svg = re.sub(r'\n\s*', '', svg)
svg = re.sub(r'<!--.+?-->', '', svg)
# end fix
ui_images[filename[:-4]] = svg
if filename.startswith('symbolLoading'):
for theme in themes:
theme_svg = re.sub(r'#808080', format_hex(theme_data[theme]['symbolDefaultColor']), svg)
write_file('%sUI/themes/%s/svg/%s' % (dev_path, theme, filename), theme_svg)
write_file('%sUI/themes/%s/svg/%s' % (min_path, theme, filename), theme_svg)
write_file(min_path + 'UI/json/UI.json', json.dumps({
'files': sorted(ui_files['min']),
'images': ui_images
}, sort_keys=True))
js = re.sub(
r'Ox.LOCALES = \{\}',
'Ox.LOCALES = ' + json.dumps(locales, indent=4, sort_keys=True),
js
)
js = re.sub(
r"Ox.VERSION = '([\d\.]+)'",
"Ox.VERSION = '%s'" % version,
js
)
*/
export default {
input: {
'Ox': 'source/Ox/Ox.js',
'UI': 'source/UI/UI.js',
'Unicode': 'source/Unicode/Unicode.js',
'Geo': 'source/Geo/Geo.js',
'Image': 'source/Image/Image.js',
},
output: {
dir: 'min',
format: 'es',
entryFileNames: '[name]/[name].js',
chunkFileNames: '[name]/[name].js',
},
plugins: [
resolve(),
terser(),
copy({
targets: [
{ src: "source/Ox.js", dest: 'min/' },
{ src: "source/Ox/json/locale.*.json", dest: 'min/Ox/json' },
{ src: "source/UI/css/*.css", dest: 'min/UI/css' },
{ src: "source/UI/json/locale.*.json", dest: 'min/UI/json' },
{ src: "source/UI/json/UI.json", dest: 'min/UI/json/' }, // FIXME: this one should be genreated first
{ src: "source/UI/png", dest: 'min/UI/' },
{ src: "source/UI/jquery/*.js", dest: 'min/UI/jquery' },
{ src: "source/UI/themes", dest: 'min/UI' },
{ src: "source/Unicode/json/*.json", dest: 'min/Unicode/json' },
{ src: "source/Geo/json/*.json", dest: 'min/Geo/json' },
{ src: "source/Geo/png/flags", dest: 'min/Geo/png/'}
],
hook: 'writeBundle'
}),
2026-02-18 20:19:59 +01:00
install(),
symlinkDevPlugin()
2026-02-18 17:02:08 +01:00
]
};