refac: separate front assets into esbuild bundles
Sort ~50 Mo de libs tierces hors du repo et regroupe le code
front en 6 bundles thématiques + 2 assets statiques.
Bundles produits par esbuild :
- core.bundle.min.js : jQuery + jQuery UI + Bootstrap 4 + validation
+ tout le code applicatif 'core'
(site, signout, signin, input-lib, md-helpers,
audiovideoinput, parallax, google.geocode,
google-geoloc). Chargé par _Layout.cshtml.
- chat.bundle.min.js : core subset (sans Bootstrap) + chat.js + comment.js
(SignalR client chargé séparément, voir plus bas)
- dropzone.bundle.min.js : jQuery + dropzone + yavsc-remote-fs
- datetime.bundle.min.js : jQuery + jQuery UI + eonasdan datetimepicker
+ jquery-timepicker
- timepicker.bundle.min.js : jQuery + jQuery UI + jquery-timepicker
- quill.bundle.min.js : Quill rich text editor
Assets statiques (non bundlés, copiés depuis node_modules) :
- signalr.min.js : client SignalR 2.x (legacy server)
- moment-with-locales.min.js : moment + 137 locales (require dynamique
non résolvable statiquement par esbuild)
CSS vendor copiées comme assets statiques vers wwwroot/css/ :
bootstrap.min.css, jquery-ui.min.css, dropzone.min.css,
dropzone-basic.min.css, bootstrap-datetimepicker.min.css,
jquery.timepicker.css
Exception gitignore : jonthornton-Datepair/jquery.datepair.min.js
n'est pas sur npm, conservé comme asset statique sous
wwwroot/lib/jonthornton-Datepair/ (téléchargé depuis upstream).
Vues Razor mises à jour (15 fichiers) pour pointer vers les bundles
et les CSS vendor. Suppression de la majorité de wwwroot/lib/ (1300+
fichiers). Suppression des jquery*.js, quill.js, showdown.js,
to-markdown.js, jquery.signalR-2.2.1.js, dropzone.js et de leurs
.min.js associés.
'jquery-datepair' et 'jquery-ui-map' non publiés sur npm :
- jquery-ui-map n'est utilisé nulle part dans le repo (vérifié),
la dépendance est purement historique.
- datepair reste en static sous wwwroot/lib/jonthornton-Datepair/.
This commit is contained in:
parent
ef4c7f40b0
commit
952d263b9f
1091 changed files with 295 additions and 333043 deletions
|
|
@ -1,109 +1,171 @@
|
|||
// Yavsc front-end bundle config (esbuild).
|
||||
//
|
||||
// Produces a single minified bundle at
|
||||
// src/Yavsc.Org/wwwroot/js/site.bundle.min.js
|
||||
// that contains both our application code and the third-party libs we
|
||||
// keep under npm. The bundle is committed to the repo so the .NET
|
||||
// runtime does not need a node toolchain at publish time.
|
||||
// Produces several thematic minified bundles under
|
||||
// src/Yavsc.Org/wwwroot/js/*.bundle.min.js
|
||||
// plus static copies of assets that cannot be bundled (legacy
|
||||
// SignalR 2.x client, moment-with-locales with its 137 dynamic
|
||||
// requires).
|
||||
//
|
||||
// Each bundle has a single entry file under build/<name>.entry.js
|
||||
// (gitignored, not committed) that re-exports the relevant vendor and
|
||||
// our code. esbuild bundles from there into one IIFE per bundle.
|
||||
//
|
||||
// Usage:
|
||||
// npm install
|
||||
// npm run build:js # one-shot
|
||||
// npm run build:js:watch # rebuild on change
|
||||
// npm run clean:js # remove the generated bundle
|
||||
// npm run clean:js # remove generated bundles + static assets
|
||||
|
||||
import { build, context } from 'esbuild';
|
||||
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
||||
import { copyFileSync, existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const isWatch = process.argv.includes('--watch');
|
||||
const isClean = process.argv.includes('--clean');
|
||||
|
||||
const outFile = path.resolve(
|
||||
__dirname,
|
||||
'src/Yavsc.Org/wwwroot/js/site.bundle.min.js'
|
||||
);
|
||||
const jsOutDir = path.resolve(__dirname, 'src/Yavsc.Org/wwwroot/js');
|
||||
const buildDir = path.resolve(__dirname, 'build');
|
||||
|
||||
const outDir = path.dirname(outFile);
|
||||
if (!existsSync(outDir)) {
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
// One bundle per thematic area. Each one points at a single entry file
|
||||
// under build/ that re-exports the relevant vendor + our code.
|
||||
const bundleNames = [
|
||||
'core', // jQuery + jQuery UI + Bootstrap 4 + validation + our core code
|
||||
'chat', // core subset + chat + comment (SignalR is loaded separately as a static asset)
|
||||
'dropzone', // jQuery + dropzone + yavsc-remote-fs
|
||||
'datetime', // jQuery + jQuery UI + eonasdan datetimepicker + jquery-timepicker
|
||||
// (moment-with-locales is a static asset, see copyStaticAssets)
|
||||
'timepicker', // jQuery + jQuery UI + jquery-timepicker
|
||||
// (datepair is not on npm; kept as a static asset for now)
|
||||
'quill', // Quill rich text editor (no jQuery dependency)
|
||||
];
|
||||
|
||||
// Static assets that cannot be bundled (dynamic require, legacy
|
||||
// server protocol, package not on npm). CSS files are also copied
|
||||
// as static assets (bundling CSS is out of scope of this commit).
|
||||
const staticAssets = [
|
||||
{
|
||||
src: 'node_modules/@aspnet/signalr/dist/browser/signalr.min.js',
|
||||
dst: 'signalr.min.js',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/moment/min/moment-with-locales.min.js',
|
||||
dst: 'moment-with-locales.min.js',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
|
||||
dst: '../css/bootstrap.min.css',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/jquery-ui/dist/themes/base/jquery-ui.min.css',
|
||||
dst: '../css/jquery-ui.min.css',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/dropzone/dist/min/dropzone.min.css',
|
||||
dst: '../css/dropzone.min.css',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/dropzone/dist/min/basic.min.css',
|
||||
dst: '../css/dropzone-basic.min.css',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css',
|
||||
dst: '../css/bootstrap-datetimepicker.min.css',
|
||||
},
|
||||
{
|
||||
src: 'node_modules/jquery-timepicker/jquery.timepicker.css',
|
||||
dst: '../css/jquery.timepicker.css',
|
||||
},
|
||||
];
|
||||
|
||||
function entryFor(bundleName) {
|
||||
return path.resolve(buildDir, `${bundleName}.entry.js`);
|
||||
}
|
||||
|
||||
// --- Our code (entry points) --------------------------------------------
|
||||
// `site.js` is the bootstrap loaded by _Layout.cshtml. We list every
|
||||
// .js we own as an entry point so they all end up in the same bundle.
|
||||
const ourEntries = [
|
||||
'src/Yavsc.Org/wwwroot/js/site.js',
|
||||
'src/Yavsc.Org/wwwroot/js/chat.js',
|
||||
'src/Yavsc.Org/wwwroot/js/comment.js',
|
||||
'src/Yavsc.Org/wwwroot/js/signout-redirect.js',
|
||||
'src/Yavsc.Org/wwwroot/js/input-lib.js',
|
||||
'src/Yavsc.Org/wwwroot/js/google-geoloc.js',
|
||||
'src/Yavsc.Org/wwwroot/js/audiovideoinput.js',
|
||||
'src/Yavsc.Org/wwwroot/js/yavsc-remote-fs.js',
|
||||
];
|
||||
function outFor(bundleName) {
|
||||
return path.join(jsOutDir, `${bundleName}.bundle.min.js`);
|
||||
}
|
||||
|
||||
// --- Third-party code we want bundled ----------------------------------
|
||||
// Listed by npm package name; resolved via node_modules.
|
||||
// jQuery, jQuery UI, jQuery plugins, Bootstrap JS bundle, SignalR
|
||||
// (browser), Moment, Quill, Showdown, To-markdown, Dropzone.
|
||||
const vendorEntries = [
|
||||
'jquery/dist/jquery.min.js',
|
||||
'jquery-ui/dist/jquery-ui.min.js',
|
||||
'jquery-validation/dist/jquery.validate.min.js',
|
||||
'jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js',
|
||||
'jquery-timepicker/jquery.timepicker.min.js',
|
||||
'jquery-datepair/dist/jquery.datepair.min.js',
|
||||
'bootstrap/dist/js/bootstrap.bundle.min.js',
|
||||
'@microsoft/signalr/dist/browser/signalr.min.js',
|
||||
'moment/min/moment.min.js',
|
||||
'showdown/dist/showdown.min.js',
|
||||
'to-markdown/dist/to-markdown.min.js',
|
||||
'dropzone/dist/min/dropzone.min.js',
|
||||
'quill/dist/quill.min.js',
|
||||
// eonasdan is registered as a CommonJS package; esbuild handles it.
|
||||
'eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js',
|
||||
];
|
||||
function buildOptionsFor(bundleName) {
|
||||
return {
|
||||
entryPoints: [entryFor(bundleName)],
|
||||
outfile: outFor(bundleName),
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap: false,
|
||||
target: ['es2018'],
|
||||
format: 'iife',
|
||||
legalComments: 'none',
|
||||
logLevel: 'info',
|
||||
nodePaths: ['node_modules'],
|
||||
};
|
||||
}
|
||||
|
||||
// Our entry points may live alongside the output, so we exclude the
|
||||
// outDir from the resolver to avoid bundling the previous run.
|
||||
const entryPoints = [...ourEntries, ...vendorEntries];
|
||||
function ensureDir(dir) {
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const buildOptions = {
|
||||
entryPoints,
|
||||
outFile,
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap: false,
|
||||
target: ['es2018'],
|
||||
format: 'iife',
|
||||
legalComments: 'none',
|
||||
logLevel: 'info',
|
||||
// jQuery plugins attach themselves to jQuery; we must not shim it.
|
||||
alias: {},
|
||||
nodePaths: ['node_modules'],
|
||||
loader: {
|
||||
'.js': 'js',
|
||||
},
|
||||
};
|
||||
function copyStaticAssets() {
|
||||
for (const a of staticAssets) {
|
||||
const src = path.resolve(__dirname, a.src);
|
||||
const dst = path.join(jsOutDir, a.dst);
|
||||
if (!existsSync(src)) {
|
||||
console.warn(`[static] ${a.src} not installed; skipping`);
|
||||
continue;
|
||||
}
|
||||
copyFileSync(src, dst);
|
||||
console.log(`[static] ${a.dst} ← ${path.relative(__dirname, src)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (isClean) {
|
||||
for (const name of bundleNames) {
|
||||
const f = outFor(name);
|
||||
if (existsSync(f)) {
|
||||
rmSync(f);
|
||||
console.log(`[clean] removed ${path.relative(__dirname, f)}`);
|
||||
}
|
||||
}
|
||||
for (const a of staticAssets) {
|
||||
const f = path.join(jsOutDir, a.dst);
|
||||
if (existsSync(f)) {
|
||||
rmSync(f);
|
||||
console.log(`[clean] removed ${path.relative(__dirname, f)}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ensureDir(jsOutDir);
|
||||
|
||||
if (isWatch) {
|
||||
const ctx = await context(buildOptions);
|
||||
await ctx.watch();
|
||||
console.log(`[esbuild] watching → ${path.relative(__dirname, outFile)}`);
|
||||
for (const name of bundleNames) {
|
||||
const ctx = await context(buildOptionsFor(name));
|
||||
await ctx.watch();
|
||||
console.log(`[esbuild] watching ${name} → ${path.basename(outFor(name))}`);
|
||||
}
|
||||
copyStaticAssets();
|
||||
console.log('[esbuild] watch mode active');
|
||||
} else {
|
||||
const result = await build(buildOptions);
|
||||
if (result.errors.length) {
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
const size = statSync(outFile).size;
|
||||
for (const name of bundleNames) {
|
||||
const opts = buildOptionsFor(name);
|
||||
const result = await build(opts);
|
||||
if (result.errors.length) {
|
||||
console.error(`[esbuild] ${name} failed`);
|
||||
process.exitCode = 1;
|
||||
continue;
|
||||
}
|
||||
const size = statSync(opts.outfile).size;
|
||||
console.log(
|
||||
`[esbuild] wrote ${path.relative(__dirname, outFile)} (${size} bytes)`
|
||||
`[esbuild] ${name} → ${path.relative(__dirname, opts.outfile)} (${size} bytes)`
|
||||
);
|
||||
}
|
||||
copyStaticAssets();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue