xref: /plugin/bpmnio/build/build-vendor.mjs (revision 033061be24b61e2ca1dcf4d5ade55358f0bb0818)
1import { build } from 'esbuild';
2import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3import path from 'node:path';
4import { fileURLToPath } from 'node:url';
5
6const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7const nodeModulesDir = path.join(rootDir, 'node_modules');
8const vendorDir = path.join(rootDir, 'vendor');
9const fontDir = path.join(rootDir, 'font');
10
11const packages = [
12  {
13    name: 'bpmn-js',
14    globalName: 'BpmnJS',
15    assetsDir: path.join('dist', 'assets'),
16    fontsDir: path.join('dist', 'assets', 'bpmn-font', 'font'),
17    fontFiles: ['bpmn.woff', 'bpmn.woff2'],
18    outputs: [
19      {
20        entryPoint: path.join(rootDir, 'build', 'vendor-entrypoints', 'bpmn-viewer.js'),
21        outFile: path.join(vendorDir, 'bpmn-js', 'dist', 'bpmn-viewer.production.min.js'),
22      },
23      {
24        entryPoint: path.join(rootDir, 'build', 'vendor-entrypoints', 'bpmn-modeler.js'),
25        outFile: path.join(vendorDir, 'bpmn-js', 'dist', 'bpmn-modeler.production.min.js'),
26      },
27    ],
28  },
29  {
30    name: 'dmn-js',
31    globalName: 'DmnJS',
32    assetsDir: path.join('dist', 'assets'),
33    fontsDir: path.join('dist', 'assets', 'dmn-font', 'font'),
34    fontFiles: ['dmn.woff', 'dmn.woff2'],
35    outputs: [
36      {
37        entryPoint: path.join(rootDir, 'build', 'vendor-entrypoints', 'dmn-viewer.js'),
38        outFile: path.join(vendorDir, 'dmn-js', 'dist', 'dmn-viewer.production.min.js'),
39      },
40      {
41        entryPoint: path.join(rootDir, 'build', 'vendor-entrypoints', 'dmn-modeler.js'),
42        outFile: path.join(vendorDir, 'dmn-js', 'dist', 'dmn-modeler.production.min.js'),
43      },
44    ],
45  },
46];
47
48async function pathExists(targetPath) {
49  try {
50    await stat(targetPath);
51    return true;
52  } catch {
53    return false;
54  }
55}
56
57async function ensureInstalled(packageName) {
58  const packagePath = path.join(nodeModulesDir, packageName, 'package.json');
59
60  if (!(await pathExists(packagePath))) {
61    throw new Error(
62      `Missing npm dependency ${packageName}. Run npm install before building vendor bundles.`
63    );
64  }
65
66  return packagePath;
67}
68
69async function readPackageMetadata(packageName) {
70  const packagePath = await ensureInstalled(packageName);
71  return JSON.parse(await readFile(packagePath, 'utf8'));
72}
73
74function createBanner(metadata) {
75  return `/*! ${metadata.name} - ${metadata.version} | generated for dokuwiki-plugin-bpmnio | ${metadata.license} */`;
76}
77
78async function copyFileEnsuringDir(sourcePath, targetPath) {
79  await mkdir(path.dirname(targetPath), { recursive: true });
80  await cp(sourcePath, targetPath, { force: true });
81}
82
83async function copyAssets(sourceDir, targetDir) {
84  await mkdir(targetDir, { recursive: true });
85
86  for (const entry of await readdir(sourceDir, { withFileTypes: true })) {
87    const sourcePath = path.join(sourceDir, entry.name);
88    const normalizedPath = sourcePath.split(path.sep).join('/');
89
90    if (normalizedPath.includes('/font/')) {
91      continue;
92    }
93
94    if (entry.isDirectory()) {
95      await copyAssets(sourcePath, path.join(targetDir, entry.name));
96      continue;
97    }
98
99    const targetName = entry.name.endsWith('.css')
100      ? entry.name.replace(/\.css$/u, '.less')
101      : entry.name;
102
103    await copyFileEnsuringDir(sourcePath, path.join(targetDir, targetName));
104  }
105}
106
107async function copyFonts(sourceDir, files) {
108  await mkdir(fontDir, { recursive: true });
109
110  for (const file of files) {
111    await copyFileEnsuringDir(path.join(sourceDir, file), path.join(fontDir, file));
112  }
113}
114
115async function cleanPackageOutput(packageName) {
116  await rm(path.join(vendorDir, packageName), { recursive: true, force: true });
117}
118
119async function copyMetadata(packageName) {
120  const sourceDir = path.join(nodeModulesDir, packageName);
121  const targetDir = path.join(vendorDir, packageName);
122
123  for (const file of ['LICENSE', 'README.md', 'package.json']) {
124    const sourcePath = path.join(sourceDir, file);
125    if (await pathExists(sourcePath)) {
126      await copyFileEnsuringDir(sourcePath, path.join(targetDir, file));
127    }
128  }
129}
130
131async function buildBundle({ entryPoint, outFile, metadata, packageName }) {
132  await mkdir(path.dirname(outFile), { recursive: true });
133
134  await build({
135    entryPoints: [entryPoint],
136    outfile: outFile,
137    bundle: true,
138    minify: true,
139    platform: 'browser',
140    format: 'iife',
141    target: ['es2019'],
142    legalComments: 'inline',
143    banner: {
144      js: createBanner(metadata),
145    },
146    define: {
147      'process.env.NODE_ENV': '"production"',
148      global: 'window',
149    },
150    logLevel: 'info',
151  });
152
153  console.log(`Built ${packageName} bundle: ${path.relative(rootDir, outFile)}`);
154}
155
156async function main() {
157  for (const pkg of packages) {
158    const metadata = await readPackageMetadata(pkg.name);
159    const sourceDir = path.join(nodeModulesDir, pkg.name);
160    const sourceAssetsDir = path.join(sourceDir, pkg.assetsDir);
161    const sourceFontsDir = path.join(sourceDir, pkg.fontsDir);
162    const targetAssetsDir = path.join(vendorDir, pkg.name, pkg.assetsDir);
163
164    await cleanPackageOutput(pkg.name);
165    await copyMetadata(pkg.name);
166    await copyAssets(sourceAssetsDir, targetAssetsDir);
167    await copyFonts(sourceFontsDir, pkg.fontFiles);
168
169    for (const output of pkg.outputs) {
170      await buildBundle({
171        entryPoint: output.entryPoint,
172        outFile: output.outFile,
173        metadata,
174        packageName: pkg.name,
175      });
176    }
177  }
178
179  const generatedMetadata = {
180    generatedAt: new Date().toISOString(),
181    packages: Object.fromEntries(
182      await Promise.all(
183        packages.map(async (pkg) => [pkg.name, (await readPackageMetadata(pkg.name)).version])
184      )
185    ),
186  };
187
188  await writeFile(
189    path.join(vendorDir, 'build-manifest.json'),
190    `${JSON.stringify(generatedMetadata, null, 2)}\n`
191  );
192}
193
194main().catch((error) => {
195  console.error(error.message);
196  process.exitCode = 1;
197});
198