Document application modes (development/debug/production) Add global file drop handler, order column normalization, SPA hash fix Serve CDN assets via /_vendor/ URLs instead of merging into bundles Add production minification with license preservation Improve JSON formatting for debugging and production optimization Add CDN asset caching with CSS URL inlining for production builds Add three-mode system (development, debug, production) Update Manifest CLAUDE.md to reflect helper class architecture Refactor Manifest.php into helper classes for better organization Pre-manifest-refactor checkpoint: Add app_mode documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
7.4 KiB
JavaScript
292 lines
7.4 KiB
JavaScript
'use strict';
|
|
|
|
const { hasOwnProperty } = Object.prototype;
|
|
const noop = function() {};
|
|
|
|
function ensureFunction(value) {
|
|
return typeof value === 'function' ? value : noop;
|
|
}
|
|
|
|
function invokeForType(fn, type) {
|
|
return function(node, item, list) {
|
|
if (node.type === type) {
|
|
fn.call(this, node, item, list);
|
|
}
|
|
};
|
|
}
|
|
|
|
function getWalkersFromStructure(name, nodeType) {
|
|
const structure = nodeType.structure;
|
|
const walkers = [];
|
|
|
|
for (const key in structure) {
|
|
if (hasOwnProperty.call(structure, key) === false) {
|
|
continue;
|
|
}
|
|
|
|
let fieldTypes = structure[key];
|
|
const walker = {
|
|
name: key,
|
|
type: false,
|
|
nullable: false
|
|
};
|
|
|
|
if (!Array.isArray(fieldTypes)) {
|
|
fieldTypes = [fieldTypes];
|
|
}
|
|
|
|
for (const fieldType of fieldTypes) {
|
|
if (fieldType === null) {
|
|
walker.nullable = true;
|
|
} else if (typeof fieldType === 'string') {
|
|
walker.type = 'node';
|
|
} else if (Array.isArray(fieldType)) {
|
|
walker.type = 'list';
|
|
}
|
|
}
|
|
|
|
if (walker.type) {
|
|
walkers.push(walker);
|
|
}
|
|
}
|
|
|
|
if (walkers.length) {
|
|
return {
|
|
context: nodeType.walkContext,
|
|
fields: walkers
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getTypesFromConfig(config) {
|
|
const types = {};
|
|
|
|
for (const name in config.node) {
|
|
if (hasOwnProperty.call(config.node, name)) {
|
|
const nodeType = config.node[name];
|
|
|
|
if (!nodeType.structure) {
|
|
throw new Error('Missed `structure` field in `' + name + '` node type definition');
|
|
}
|
|
|
|
types[name] = getWalkersFromStructure(name, nodeType);
|
|
}
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
function createTypeIterator(config, reverse) {
|
|
const fields = config.fields.slice();
|
|
const contextName = config.context;
|
|
const useContext = typeof contextName === 'string';
|
|
|
|
if (reverse) {
|
|
fields.reverse();
|
|
}
|
|
|
|
return function(node, context, walk, walkReducer) {
|
|
let prevContextValue;
|
|
|
|
if (useContext) {
|
|
prevContextValue = context[contextName];
|
|
context[contextName] = node;
|
|
}
|
|
|
|
for (const field of fields) {
|
|
const ref = node[field.name];
|
|
|
|
if (!field.nullable || ref) {
|
|
if (field.type === 'list') {
|
|
const breakWalk = reverse
|
|
? ref.reduceRight(walkReducer, false)
|
|
: ref.reduce(walkReducer, false);
|
|
|
|
if (breakWalk) {
|
|
return true;
|
|
}
|
|
} else if (walk(ref)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (useContext) {
|
|
context[contextName] = prevContextValue;
|
|
}
|
|
};
|
|
}
|
|
|
|
function createFastTraveralMap({
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block,
|
|
DeclarationList
|
|
}) {
|
|
return {
|
|
Atrule: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block
|
|
},
|
|
Rule: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block
|
|
},
|
|
Declaration: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block,
|
|
DeclarationList
|
|
}
|
|
};
|
|
}
|
|
|
|
function createWalker(config) {
|
|
const types = getTypesFromConfig(config);
|
|
const iteratorsNatural = {};
|
|
const iteratorsReverse = {};
|
|
const breakWalk = Symbol('break-walk');
|
|
const skipNode = Symbol('skip-node');
|
|
|
|
for (const name in types) {
|
|
if (hasOwnProperty.call(types, name) && types[name] !== null) {
|
|
iteratorsNatural[name] = createTypeIterator(types[name], false);
|
|
iteratorsReverse[name] = createTypeIterator(types[name], true);
|
|
}
|
|
}
|
|
|
|
const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
|
|
const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
|
|
|
|
const walk = function(root, options) {
|
|
function walkNode(node, item, list) {
|
|
const enterRet = enter.call(context, node, item, list);
|
|
|
|
if (enterRet === breakWalk) {
|
|
return true;
|
|
}
|
|
|
|
if (enterRet === skipNode) {
|
|
return false;
|
|
}
|
|
|
|
if (iterators.hasOwnProperty(node.type)) {
|
|
if (iterators[node.type](node, context, walkNode, walkReducer)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (leave.call(context, node, item, list) === breakWalk) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
let enter = noop;
|
|
let leave = noop;
|
|
let iterators = iteratorsNatural;
|
|
let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
|
|
const context = {
|
|
break: breakWalk,
|
|
skip: skipNode,
|
|
|
|
root,
|
|
stylesheet: null,
|
|
atrule: null,
|
|
atrulePrelude: null,
|
|
rule: null,
|
|
selector: null,
|
|
block: null,
|
|
declaration: null,
|
|
function: null
|
|
};
|
|
|
|
if (typeof options === 'function') {
|
|
enter = options;
|
|
} else if (options) {
|
|
enter = ensureFunction(options.enter);
|
|
leave = ensureFunction(options.leave);
|
|
|
|
if (options.reverse) {
|
|
iterators = iteratorsReverse;
|
|
}
|
|
|
|
if (options.visit) {
|
|
if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
|
|
iterators = options.reverse
|
|
? fastTraversalIteratorsReverse[options.visit]
|
|
: fastTraversalIteratorsNatural[options.visit];
|
|
} else if (!types.hasOwnProperty(options.visit)) {
|
|
throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).sort().join(', ') + ')');
|
|
}
|
|
|
|
enter = invokeForType(enter, options.visit);
|
|
leave = invokeForType(leave, options.visit);
|
|
}
|
|
}
|
|
|
|
if (enter === noop && leave === noop) {
|
|
throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
|
|
}
|
|
|
|
walkNode(root);
|
|
};
|
|
|
|
walk.break = breakWalk;
|
|
walk.skip = skipNode;
|
|
|
|
walk.find = function(ast, fn) {
|
|
let found = null;
|
|
|
|
walk(ast, function(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found = node;
|
|
return breakWalk;
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
walk.findLast = function(ast, fn) {
|
|
let found = null;
|
|
|
|
walk(ast, {
|
|
reverse: true,
|
|
enter(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found = node;
|
|
return breakWalk;
|
|
}
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
walk.findAll = function(ast, fn) {
|
|
const found = [];
|
|
|
|
walk(ast, function(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found.push(node);
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
return walk;
|
|
}
|
|
|
|
exports.createWalker = createWalker;
|