Stable initial build of module replacement.
parent
3f1618e0c5
commit
0ad6124c29
|
@ -12,7 +12,9 @@
|
|||
"hast-util-from-html": "^1.0.1",
|
||||
"hast-util-to-html": "^8.0.4",
|
||||
"hastscript": "^7.2.0",
|
||||
"unist-util-visit": "^4.1.2"
|
||||
"unist-util-visit": "^4.1.2",
|
||||
"xast-util-from-xml": "^3.0.0",
|
||||
"xast-util-to-xml": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
|
@ -2671,6 +2673,14 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@rgrove/parse-xml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rgrove/parse-xml/-/parse-xml-4.1.0.tgz",
|
||||
"integrity": "sha512-pBiltENdy8SfI0AeR1e5TRpS9/9Gl0eiOEt6ful2jQfzsgvZYWqsKiBWaOCLdocQuk0wS7KOHI37n0C1pnKqTw==",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.25.24",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||
|
@ -2859,6 +2869,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"node_modules/@types/xast": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/xast/-/xast-1.0.2.tgz",
|
||||
"integrity": "sha512-eIuX49CmWaXTh0hDEQTFH5MbTV+NPm0FM5gB36nDcr9x8n75t9o7wy2guS1lakkQxtUwfVDYiokqvHk3x0CC4Q==",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
|
||||
|
@ -9100,6 +9118,35 @@
|
|||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xast-util-from-xml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xast-util-from-xml/-/xast-util-from-xml-3.0.0.tgz",
|
||||
"integrity": "sha512-zEfGxnue7vHbayZnRBAjJsjV1dQG6XWKLAzrsPxX2l4WHmqGZxM76XxRblaTHvcOYMFLlnVp3DFcUAl+Qk9SNQ==",
|
||||
"dependencies": {
|
||||
"@rgrove/parse-xml": "^4.1.0",
|
||||
"@types/xast": "^1.0.0",
|
||||
"vfile-location": "^4.0.0",
|
||||
"vfile-message": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/xast-util-to-xml": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xast-util-to-xml/-/xast-util-to-xml-3.0.2.tgz",
|
||||
"integrity": "sha512-lbnJEVui7Lk9W/TydInkin5OnOyTAr2b4OpK5YjXo94qqoXaR6mFE/evXfOrBev9EnD91r0FOPxrLfTT0qLITQ==",
|
||||
"dependencies": {
|
||||
"@types/xast": "^1.0.0",
|
||||
"ccount": "^2.0.0",
|
||||
"stringify-entities": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz",
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
"hast-util-from-html": "^1.0.1",
|
||||
"hast-util-to-html": "^8.0.4",
|
||||
"hastscript": "^7.2.0",
|
||||
"unist-util-visit": "^4.1.2"
|
||||
"unist-util-visit": "^4.1.2",
|
||||
"xast-util-from-xml": "^3.0.0",
|
||||
"xast-util-to-xml": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import {visit} from 'unist-util-visit';
|
||||
|
||||
/**
|
||||
* applyModules({ast,modules})
|
||||
*
|
||||
* Given an abstract syntax tree and a catalog of modules, apply the modules
|
||||
* to the AST.
|
||||
*
|
||||
* @param {object} An object containing an abstract syntax tree (ast) and a
|
||||
* catalog of modules (modules).
|
||||
* @returns {ast, modules}
|
||||
*/
|
||||
export default async function applyModules ({ast, modules}) {
|
||||
const result = {};
|
||||
|
||||
visit(
|
||||
ast,
|
||||
// A filter. True means visit this node, false means don't. Optional
|
||||
// param.
|
||||
node => {
|
||||
if (!node?.children?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through this node's children. If any of them have a name that
|
||||
// matches a defined module, we want to use this node (so we can
|
||||
// replace the child with the component's content).
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
if (Object.keys(modules).includes(node.children[i].name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// This is the what-to-do-with-the-nodes function.
|
||||
node => {
|
||||
// Loop through each child. If the child's name matches a component's
|
||||
// registered name, replace the child with the component's contents.
|
||||
for (let c = 0; c < node.children.length; c++) {
|
||||
if (Object.keys(modules).includes(node.children[c].name)) {
|
||||
node.children.splice(c,1,...modules[node.children[c].name].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {ast, modules};
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import {visit} from 'unist-util-visit';
|
||||
|
||||
/**
|
||||
* extractModules()
|
||||
*
|
||||
* Given an abstract syntax tree, remove loom modules. Return the remaining
|
||||
* AST and a catalog of modules.
|
||||
*
|
||||
* @param {ast} ast An abstract syntax tree from XML.
|
||||
* @returns { modules, ast }
|
||||
*/
|
||||
export default async function extractModules (ast) {
|
||||
const modules = {};
|
||||
|
||||
visit(
|
||||
ast,
|
||||
node => {
|
||||
if (!node?.children?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
if (node.children[i].name === 'loom-module') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
node => {
|
||||
node.children = node.children.reduce(
|
||||
(list,item) => {
|
||||
if (item.name === 'loom-module') {
|
||||
if (item.attributes?.define) {
|
||||
modules[item.attributes?.define] = item;
|
||||
}
|
||||
} else {
|
||||
list.push(item);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return {modules, ast};
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import {fromXml} from 'xast-util-from-xml';
|
||||
/**
|
||||
* fileToAst(fn)
|
||||
*
|
||||
* Given a filename, parse it as XML to an abstract syntax tree.
|
||||
*
|
||||
* @param {string} The path to an XML file.
|
||||
* @returns {ast} An abstract syntax tree of the XHTML file.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default async function fileToAst (fn) {
|
||||
const html = await fs.readFile(fn);
|
||||
const tree = fromXml(
|
||||
`<LOOM-FRAGMENT>${html}</LOOM-FRAGMENT>`,
|
||||
{depth: null}
|
||||
);
|
||||
return tree;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import extractModules from './src/extract-modules.js';
|
||||
import applyModules from './src/apply-modules.js';
|
||||
import fileToAst from './src/file-to-ast.js';
|
||||
import {toXml} from 'xast-util-to-xml';
|
||||
|
||||
fileToAst('./tmp/demo.html')
|
||||
.then(extractModules)
|
||||
.then(applyModules)
|
||||
.then(({ast,modules}) => {
|
||||
console.log(toXml(ast.children[0].children));
|
||||
});
|
||||
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<loom-module define="Component">
|
||||
<span>component</span>
|
||||
foo
|
||||
</loom-module>
|
||||
|
||||
<div>
|
||||
|
@ -7,3 +8,9 @@
|
|||
<span class="foo" id="bar">testing</span>
|
||||
<Component/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>Title</h1>
|
||||
<span class="foo" id="bar">testing</span>
|
||||
<Component/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as fs from 'fs/promises'
|
||||
import { fromHtml } from 'hast-util-from-html'
|
||||
import { toHtml } from 'hast-util-to-html'
|
||||
import {fromXml} from 'xast-util-from-xml'
|
||||
|
||||
import { toXml } from 'xast-util-to-xml'
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
|
||||
|
@ -10,12 +11,12 @@ async function readHtml (fn) {
|
|||
|
||||
async function fileToAst () {
|
||||
const html = await readHtml('./demo.html');
|
||||
const tree = fromHtml(html, {fragment: true})
|
||||
const tree = fromXml(`<LOOM-FRAGMENT>${html}</LOOM-FRAGMENT>`, {depth: null})
|
||||
return tree;
|
||||
}
|
||||
|
||||
async function extractModules (ast) {
|
||||
const modules = [];
|
||||
const modules = {};
|
||||
|
||||
visit(
|
||||
ast,
|
||||
|
@ -25,7 +26,7 @@ async function extractModules (ast) {
|
|||
}
|
||||
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
if (node.children[i].tagName === 'loom-module') {
|
||||
if (node.children[i].name === 'loom-module') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +34,10 @@ async function extractModules (ast) {
|
|||
node => {
|
||||
node.children = node.children.reduce(
|
||||
(list,item) => {
|
||||
if (item.tagName === 'loom-module') {
|
||||
console.log("foo")
|
||||
modules.push(item);
|
||||
if (item.name === 'loom-module') {
|
||||
if (item.attributes?.define) {
|
||||
modules[item.attributes?.define] = item;
|
||||
}
|
||||
} else {
|
||||
list.push(item);
|
||||
}
|
||||
|
@ -49,15 +51,50 @@ async function extractModules (ast) {
|
|||
return {modules, ast};
|
||||
}
|
||||
|
||||
async function applyModules ({ast, modules}) {
|
||||
const result = {};
|
||||
|
||||
visit(
|
||||
ast,
|
||||
// A filter. True means visit this node, false means don't. Optional
|
||||
// param.
|
||||
node => {
|
||||
if (!node?.children?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through this node's children. If any of them have a name that
|
||||
// matches a defined module, we want to use this node (so we can
|
||||
// replace the child with the component's content).
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
if (Object.keys(modules).includes(node.children[i].name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// This is the what-to-do-with-the-nodes function.
|
||||
node => {
|
||||
// Loop through each child. If the child's name matches a component's
|
||||
// registered name, replace the child with the component's contents.
|
||||
for (let c = 0; c < node.children.length; c++) {
|
||||
if (Object.keys(modules).includes(node.children[c].name)) {
|
||||
node.children.splice(c,1,...modules[node.children[c].name].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {ast, modules};
|
||||
}
|
||||
|
||||
|
||||
fileToAst()
|
||||
.then(ast => {
|
||||
return ast;
|
||||
})
|
||||
.then(extractModules)
|
||||
.then(applyModules)
|
||||
.then(({ast,modules}) => {
|
||||
console.log(JSON.stringify(ast,undefined,2));
|
||||
console.log(toHtml(ast))
|
||||
console.log(modules);
|
||||
console.log(toXml(ast.children[0].children))
|
||||
})
|
||||
|
||||
|
||||
|
|
29
tmp/test.js
29
tmp/test.js
|
@ -1,19 +1,16 @@
|
|||
import {h, s} from 'hastscript'
|
||||
|
||||
console.log(
|
||||
h('.foo#some-id', [
|
||||
h('span', 'some text'),
|
||||
h('input', {type: 'text', value: 'foo'}),
|
||||
h('a.alpha', {class: 'bravo charlie', download: 'download'}, [
|
||||
'delta',
|
||||
'echo'
|
||||
])
|
||||
])
|
||||
)
|
||||
import {fromXml} from 'xast-util-from-xml';
|
||||
|
||||
const xml = `
|
||||
<album id="123">
|
||||
<name>Born in the U.S.A.</name>
|
||||
<artist>Bruce Springsteen</artist>
|
||||
<releasedate>1984-04-06</releasedate>
|
||||
</album>
|
||||
`
|
||||
|
||||
const tree = fromXml(xml);
|
||||
|
||||
console.dir(tree, {depth:null});
|
||||
|
||||
console.log(
|
||||
s('svg', {xmlns: 'http://www.w3.org/2000/svg', viewbox: '0 0 500 500'}, [
|
||||
s('title', 'SVG `<circle>` element'),
|
||||
s('circle', {cx: 120, cy: 120, r: 100})
|
||||
])
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue