Skip to content

Commit

Permalink
feat(core): simple ast modification for island ast injection
Browse files Browse the repository at this point in the history
  • Loading branch information
barelyhuman committed Feb 14, 2024
1 parent d0df466 commit d3cb518
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 7 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 21 additions & 5 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import type { Program, Node, ExportNamedDeclaration } from 'acorn'

type IslandNode = {
id: string
node: Node
nodeItem: ExportNamedDeclaration
ast: Program
}

export function readSourceFile(file: any): any
/**
* @param {string} sourceCode the javascript/typescript source code to look for islands in
Expand All @@ -13,11 +22,7 @@ export function findIslands(
}?: {
isFunctionIsland: typeof isFunctionIsland
}
): {
id: any
node: any
nodeItem: any
}[]
): IslandNode[]
export function islandNodeToTemplate(island: any): {
server: any
client: string
Expand All @@ -44,3 +49,14 @@ export function isFunctionIsland(
): boolean
export function getIslandName(name: any): string
export const DEFAULT_TRANSPILED_IDENTIFIERS: string[]

/**
* NOT-PURE
* Modifies the original AST to include the
* passed island item, replacing the original ast
*/
export function injectIslandAST(
sourceAST: Program,
island: IslandNode,
templateGenerator = typeof generateServerTemplate
): void
27 changes: 26 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function readSourceFile(file) {
* @param {object} options
* @param {typeof isFunctionIsland} options.isFunctionIsland a function that validates a functional node to classify it
* as an island or not, returns a truthy value if it finds an island in the passed AST. Defaults to the `isFunctionIsland` export from this library
* @returns
* @returns {import(".").IslandNode[]}
*/
export function findIslands(
sourceCode,
Expand Down Expand Up @@ -88,6 +88,7 @@ export function findIslands(
id,
node: nodeItem.node,
nodeItem,
ast,
})
}
}
Expand Down Expand Up @@ -217,6 +218,30 @@ export function generateServerTemplate(name) {
return code
}

/**
* NOT-PURE
* Modifies the original AST to include the
* passed island item, replacing the original ast
* @param {import("acorn").Program} sourceAST
* @param {import(".").IslandNode} island
* @param {typeof generateServerTemplate} templateGenerator
*/
export function injectIslandAST(
sourceAST,
island,
templateGenerator = generateServerTemplate
) {
const astBody = sourceAST.body
astBody.forEach((item, index) => {
if (item !== island.node) {
return
}
const serverTemplate = templateGenerator(island.id)
const islandItem = astFromCode(serverTemplate).body
astBody.splice(index, 1, island.node.declaration, ...islandItem)
})
}

export function generateClientTemplate(name) {
const islandName = getIslandName(name)

Expand Down
39 changes: 39 additions & 0 deletions tests/inject.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { test } from 'uvu'
import * as assert from 'uvu/assert'
import { findIslands, injectIslandAST } from '../src/index.js'
import { codeFromAST } from '../src/ast.js'

test('inject island code into the existing ast', () => {
const islands = findIslands(`
// @jsx h
import { h } from "preact";
import { useState } from "preact/hooks";
const counter = 0;
export const Component = () => {
const onClick = () => {};
return h("button", { onClick: onClick, children: "hello" });
};
export const Component2 = () => {
const [state, setState] = useState(0)
return h("button", { children: state });
}
`)

assert.equal(islands.length, 2)

islands.forEach(island => {
injectIslandAST(island.ast, island)
})

const code = codeFromAST(islands[0].ast)
assert.ok(code.includes('IslandComponent'))
assert.ok(code.includes('IslandComponent2'))
assert.ok(code.includes('const counter'))
assert.not.ok(code.includes('export const Component'))
assert.not.ok(code.includes('export const Component2'))
})

test.run()

0 comments on commit d3cb518

Please sign in to comment.