UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

93 lines (81 loc) 4.45 kB
module.exports = exports = (m,o) => m.meta?.minified ? m : (new Minifier) .minify (m,o) class Minifier { /** * Minifies a model by removing all definitions not reachable from given roots. * @param {object} [options] - Options for the minification as follows... * @param {kinds} [options.keep] Controls which children of services to keep. * @param {string[]} [options.cleanse] Names of properties or annotations to be removed. * @typedef {{ entity, aspect, type, event, action, function }} kinds * @returns {CSN.Model} the minified model with kept definitions only. */ minify (csn, options, { skip_unused } = global.cds.env.features) { const o = this.options = options || {} if (skip_unused === false) return csn if (skip_unused === 'services') o.services = 'all' const all = new Map (Object.entries( this.defs ??= csn.definitions || {} )) const children = (n,fn,pre=n+'.') => { for (let [n,d] of all) if (n.startsWith(pre)) fn (n,d) } const events = { event:1, action:1, function:1 } const keep = o.keep ??= { entity:1, type:1, ...events } const kept = this.kept = csn.definitions = {} const skipped = this.skipped = {} if (o.services) { // If o.services is specified, only keep matching services and their children const rx = o.services == 'all' || o.services == '/all/i' ? {test:()=>true} : o.services for (let [s,d] of all) if (d.kind === 'service' && rx.test(s)) { this.keep (s,d); children (s, (c,d) => this.keep (c,d)) } } else { // Otherwise first mark all external services and their children as initially skipped for (let [s,d] of all) if (d.kind === 'service' && _skip_service(s,d)) { skipped[s] = 0; children (s, (c,d) => d.kind in events ? this.keep (c,d) : skipped[c] = s) // used later on in this.keep() } // Then keep all own services and their children for (let [s,d] of all) if (d.kind === 'service' && !(s in skipped)) { this.keep (s,d); children (s, (c,d) => d.kind in keep ? this.keep (c,d) : skipped[c] = 0) } // Also keep remaining non-service entities for (let [e,d] of all) if (d.kind === 'entity') { e in kept || e in skipped || _skip_entity(e,d) || this.keep (e,d) } } ;(csn.meta ??= {}) .minified = true return csn } cleanse (d, o = this.options, keep = this._keep ??= Object.keys (o.keep)) { for (let p in o.cleanse) { if (p[0] !== '@') delete d[p] // a single property for (let a in d) if (a.startsWith(p) && !keep.some(k => a.startsWith(k))) delete d[a] } } walk (d) { this.cleanse(d) if (d.target) this.keep (d.target) // has to go first w/o return for redirected targets if (d.type in this.defs) return this.keep (d.type) // return to avoid endless recursion if (d.type?.ref) return this.keep (d.type.ref[0]) // return to avoid endless recursion if (d.projection) this.view (d.projection) if (d.query) this.view (d.query) if (d.items) this.walk (d.items) if (d.returns) this.walk (d.returns) for (let e in d.elements) this.walk (d.elements[e]) for (let a in d.actions) this.walk (d.actions[a]) for (let p in d.params) this.walk (d.params[p]) for (let i in d.includes) this.keep (d.includes[i]) // Note: this ^^^^^^^^^^^^ is required for cdsc.recompile; with delete d.includes, redirects in AFC broke } view (q) { if (q.SELECT) q = q.SELECT // i.e. entity as select from ... if (q.mixin) for (let e in q.mixin) this.walk (q.mixin[e]) if (q.from?.ref) return this.keep (_source(q.from.ref[0])) // keep sources of views if (q.from?.join) return q.from.args.forEach (from => this.view ({from})) if (q.SET) return q.SET.args.forEach (q => this.view (q.SELECT||q)) function _source (r) { return r.id || r } } keep (n,d) { if (n in this.kept) return; else d ??= this.defs[n] if (d) this.walk (this.kept[n] = d, n); else return let texts = this.defs[n+'.texts']; if (texts) this.keep (n+'.texts', texts) let parent = this.skipped[n]; if (parent) this.keep(parent) // keep initially skipped services } } const _skip_service = (s,d) => d['@cds.external'] >= 2 const _skip_entity = (e,d) => d['@cds.external'] >= 2 || d['@cds.persistence.skip'] === 'if-unused' || e.endsWith('.texts') exports.Minifier = Minifier