Replace rollup-plugin-license with rolldown-license-plugin (#37130)

Replace `rollup-plugin-license` and `wrap-ansi` with
[`rolldown-license-plugin`](https://github.com/silverwind/rolldown-license-plugin),
a zero-dependency plugin with async parallel I/O and built-in word
wrapping.

- Removes `rollup-plugin-license` (pulls in `lodash`, `moment`) and
`wrap-ansi` from the dependency tree
- License build time reduced by ~40% (370ms vs 640ms)
- Added e2e test for `licenses.txt`

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
This commit is contained in:
silverwind
2026-04-09 11:31:05 +02:00
committed by GitHub
parent 0914a44a9b
commit 04fb6f1c0b
5 changed files with 41 additions and 394 deletions

View File

@@ -1,13 +1,12 @@
import {build, defineConfig} from 'vite';
import vuePlugin from '@vitejs/plugin-vue';
import {stringPlugin} from 'vite-string-plugin';
import {licensePlugin, wrap} from 'rolldown-license-plugin';
import {readFileSync, writeFileSync, mkdirSync, unlinkSync, globSync} from 'node:fs';
import path, {basename, join, parse} from 'node:path';
import {env} from 'node:process';
import tailwindcss from 'tailwindcss';
import tailwindConfig from './tailwind.config.ts';
import wrapAnsi from 'wrap-ansi';
import licensePlugin from 'rollup-plugin-license';
import type {InlineConfig, Plugin, Rolldown} from 'vite';
import {camelize} from 'vue';
@@ -39,10 +38,6 @@ const webComponents = new Set([
'text-expander',
]);
function formatLicenseText(licenseText: string) {
return wrapAnsi(licenseText || '', 80).trim();
}
const commonRolldownOptions: Rolldown.RolldownOptions = {
checks: {
eval: false, // htmx needs eval
@@ -314,33 +309,29 @@ export default defineConfig(commonViteOpts({
},
}),
isProduction ? licensePlugin({
thirdParty: {
output: {
file: join(import.meta.dirname, 'public/assets/licenses.txt'),
template(deps) {
const line = '-'.repeat(80);
const goJson = readFileSync(join(import.meta.dirname, 'assets/go-licenses.json'), 'utf8');
const goModules = JSON.parse(goJson).map(({name, licenseText}: {name: string, licenseText: string}) => {
return {name, body: formatLicenseText(licenseText)};
});
const jsModules = deps.map((dep) => {
return {name: dep.name, version: dep.version, body: formatLicenseText(dep.licenseText ?? '')};
});
const modules = [...goModules, ...jsModules].sort((a, b) => a.name.localeCompare(b.name));
return modules.map(({name, version, body}: {name: string, version?: string, body: string}) => {
const title = version ? `${name}@${version}` : name;
return `${line}\n${title}\n${line}\n${body}`;
}).join('\n');
},
},
allow(dependency) {
if (dependency.name === 'khroma') return true; // MIT: https://github.com/fabiospampinato/khroma/pull/33
return /(Apache-2\.0|0BSD|BSD-2-Clause|BSD-3-Clause|MIT|ISC|CPAL-1\.0|Unlicense|EPL-1\.0|EPL-2\.0)/.test(dependency.license ?? '');
},
done(deps, context) {
const line = '-'.repeat(80);
const goLicenses = JSON.parse(readFileSync(join(import.meta.dirname, 'assets/go-licenses.json'), 'utf8'));
const combined: Record<string, string> = {};
for (const {name, licenseText} of goLicenses) {
combined[name] = wrap(licenseText || '', 80).trim();
}
for (const {name, version, licenseText} of deps) {
combined[`${name}@${version}`] = wrap(licenseText, 80).trim();
}
const content = Object.entries(combined)
.sort(([a], [b]) => a.localeCompare(b))
.map(([title, body]) => `${line}\n${title}\n${line}\n${body}`).join('\n');
context.emitFile({type: 'asset', fileName: 'licenses.txt', source: content});
},
match: /^((UN)?LICEN(S|C)E|COPYING).*$/i, // also defined in build/generate-go-licenses.go
allow(dep) {
if (dep.name === 'khroma') return true; // MIT: https://github.com/fabiospampinato/khroma/pull/33
return /(Apache-2\.0|0BSD|BSD-2-Clause|BSD-3-Clause|MIT|ISC|CPAL-1\.0|Unlicense|EPL-1\.0|EPL-2\.0)/.test(dep.license);
},
}) : {
name: 'dev-licenses-stub',
closeBundle() {
configureServer() {
writeFileSync(join(outDir, 'licenses.txt'), 'Licenses are disabled during development');
},
},