!159 fix(transpiler): auto import
* fix(transpiler): auto import * feat(transpiler): auto import
This commit is contained in:
parent
2640177de5
commit
2445208856
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "inula-novdom",
|
"name": "@inula/no-vdom",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "no vdom runtime",
|
"description": "no vdom runtime",
|
||||||
"main": "index.js",
|
"main": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest --ui",
|
"test": "vitest --ui",
|
||||||
"bench": "vitest bench",
|
"bench": "vitest bench",
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './Cond';
|
||||||
|
export * from './Dynamic';
|
||||||
|
export * from './Env';
|
||||||
|
export * from './For';
|
|
@ -61,6 +61,7 @@ function watchRender(fn: (value: any) => any, initial?: any): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertExpression(parent: Node, value: any, prevValue: any, marker?: Node): any {
|
function insertExpression(parent: Node, value: any, prevValue: any, marker?: Node): any {
|
||||||
|
Array.isArray(value) && value.length === 1 && (value = value[0]);
|
||||||
let result: any;
|
let result: any;
|
||||||
while (typeof prevValue === 'function') {
|
while (typeof prevValue === 'function') {
|
||||||
prevValue = prevValue();
|
prevValue = prevValue();
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from 'inula-reactive';
|
||||||
|
export * from './core';
|
||||||
|
export * from './dom';
|
||||||
|
export * from './event';
|
||||||
|
export * from './components';
|
||||||
|
export * from './type';
|
|
@ -17,30 +17,15 @@
|
||||||
|
|
||||||
import { describe, expect } from 'vitest';
|
import { describe, expect } from 'vitest';
|
||||||
import { domTest as it } from './utils';
|
import { domTest as it } from './utils';
|
||||||
import { template as $$template, insert as $$insert } from '../src/dom';
|
import { render, Dynamic, reactive } from '@inula/no-vdom';
|
||||||
import { runComponent as $$runComponent, render } from '../src/core';
|
|
||||||
import { Dynamic } from '../src/components/Dynamic';
|
|
||||||
import { reactive } from 'inula-reactive';
|
|
||||||
|
|
||||||
describe('Dynamic', () => {
|
describe('Dynamic', () => {
|
||||||
it('should work with native elements.', ({ container }) => {
|
it('should work with native elements.', ({ container }) => {
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* function App() {
|
|
||||||
* return <Dynamic component="h1">foo</Dynamic>;
|
|
||||||
* }
|
|
||||||
* render(() => <App />, container);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 编译后:
|
|
||||||
function App() {
|
function App() {
|
||||||
return $$runComponent(Dynamic, {
|
return <Dynamic component="h1">foo</Dynamic>;
|
||||||
component: 'h1',
|
|
||||||
children: 'foo',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(() => $$runComponent(App, {}), container);
|
render(() => <App />, container);
|
||||||
expect(container).toMatchInlineSnapshot(`
|
expect(container).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
<h1>
|
<h1>
|
||||||
|
@ -51,36 +36,15 @@ describe('Dynamic', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with components.', ({ container }) => {
|
it('should work with components.', ({ container }) => {
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* function App() {
|
|
||||||
* return <Dynamic component={Title} name="bar"/>;
|
|
||||||
* }
|
|
||||||
* function Title(props) {
|
|
||||||
* return <h1>{props.name}</h1>;
|
|
||||||
* }
|
|
||||||
* render(() => <App />, container);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 编译后:
|
|
||||||
function App() {
|
function App() {
|
||||||
return $$runComponent(Dynamic, {
|
return <Dynamic component={Title} name="bar" />;
|
||||||
component: Title,
|
|
||||||
name: 'bar',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _tmpl = /*#__PURE__*/ $$template('<h1>');
|
|
||||||
|
|
||||||
function Title(props) {
|
function Title(props) {
|
||||||
return (() => {
|
return <h1>{props.name}</h1>;
|
||||||
const _el$ = _tmpl();
|
|
||||||
$$insert(_el$, () => props.name, null);
|
|
||||||
return _el$;
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(() => $$runComponent(App, {}), container);
|
render(() => <App />, container);
|
||||||
expect(container).toMatchInlineSnapshot(`
|
expect(container).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
<h1>
|
<h1>
|
||||||
|
@ -91,62 +55,16 @@ describe('Dynamic', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on invalid component.', ({ container }) => {
|
it('should throw on invalid component.', ({ container }) => {
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* function App() {
|
|
||||||
* return <Dynamic component={null} />;
|
|
||||||
* }
|
|
||||||
* render(() => <App />, container);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 编译后:
|
|
||||||
function App() {
|
function App() {
|
||||||
return $$runComponent(Dynamic, {
|
return <Dynamic component={null} />;
|
||||||
component: null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => render(() => $$runComponent(App, {}), container)).toThrowError('Invalid component for Dynamic');
|
expect(() => render(() => <App />, container)).toThrowError('Invalid component for Dynamic');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change component.', async ({ container }) => {
|
it('should change component.', async ({ container }) => {
|
||||||
/**
|
const H1 = props => <h1>{props.children}</h1>;
|
||||||
* 源码:
|
const H3 = props => <h3>{props.children}</h3>;
|
||||||
* const H1 = (props) => <h1>{props.children}</h1>;
|
|
||||||
* const H3 = (props) => <h3>{props.children}</h3>;
|
|
||||||
* function App() {
|
|
||||||
* const comp = reactive('h1');
|
|
||||||
* const comps = {
|
|
||||||
* H1,
|
|
||||||
* H3,
|
|
||||||
* h1: 'h1',
|
|
||||||
* h2: 'h2',
|
|
||||||
* }
|
|
||||||
* return (
|
|
||||||
* <div>
|
|
||||||
* <Dynamic component={comps[comp.get()]}>foo</Dynamic>
|
|
||||||
* </div>
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
* render(() => <App />, container);
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 编译后:
|
|
||||||
const _tmpl$ = /*#__PURE__*/ $$template('<div></div>');
|
|
||||||
const _h1 = /*#__PURE__*/ $$template('<h1>'),
|
|
||||||
_h3 = /*#__PURE__*/ $$template('<h3>');
|
|
||||||
const H1 = (props: { children: any }) => {
|
|
||||||
const _el$ = _h1();
|
|
||||||
$$insert(_el$, () => props.children);
|
|
||||||
return _el$;
|
|
||||||
};
|
|
||||||
const H3 = (props: { children: any }) => {
|
|
||||||
const _el$3 = _h3();
|
|
||||||
$$insert(_el$3, () => props.children);
|
|
||||||
return _el$3;
|
|
||||||
};
|
|
||||||
|
|
||||||
const comp = reactive('h1');
|
const comp = reactive('h1');
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
@ -156,22 +74,15 @@ describe('Dynamic', () => {
|
||||||
h1: 'h1',
|
h1: 'h1',
|
||||||
h2: 'h2',
|
h2: 'h2',
|
||||||
};
|
};
|
||||||
return (() => {
|
return (
|
||||||
const _div = _tmpl$();
|
<div>
|
||||||
$$insert(
|
<Dynamic component={comps[comp.get()]}>foo</Dynamic>
|
||||||
_div,
|
</div>
|
||||||
$$runComponent(Dynamic, {
|
);
|
||||||
get component() {
|
|
||||||
return comps[comp.get()];
|
|
||||||
},
|
|
||||||
children: 'foo',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return _div;
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(() => $$runComponent(App, {}), container);
|
render(() => <App />, container);
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h1>foo</h1></div>"');
|
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h1>foo</h1></div>"');
|
||||||
comp.set('h2');
|
comp.set('h2');
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h2>foo</h2></div>"');
|
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h2>foo</h2></div>"');
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"paths": {
|
||||||
|
"@inula/no-vdom": [
|
||||||
|
"./src/index.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esModuleInterop": true
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,21 @@
|
||||||
// vitest.config.ts
|
// vitest.config.ts
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
import inula from 'vite-plugin-inula-no-vdom';
|
import inula from 'vite-plugin-inula-no-vdom';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
esbuild: {
|
esbuild: {
|
||||||
jsx: 'preserve',
|
jsx: 'preserve',
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@inula/no-vdom': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
conditions: ['dev'],
|
conditions: ['dev'],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// @ts-expect-error TODO: fix vite plugin interface is not compatible
|
// @ts-expect-error TODO: fix vite plugin interface is not compatible
|
||||||
inula({ packageName: 'inula-reactive' }),
|
inula(),
|
||||||
],
|
],
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom', // or 'jsdom', 'node'
|
environment: 'jsdom', // or 'jsdom', 'node'
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const importMap = [
|
||||||
'addEventListener',
|
'addEventListener',
|
||||||
'watch',
|
'watch',
|
||||||
'insert',
|
'insert',
|
||||||
'createComponent',
|
'runComponent',
|
||||||
'createText'
|
'createText'
|
||||||
].reduce<Record<string, string>>((acc, cur) => {
|
].reduce<Record<string, string>>((acc, cur) => {
|
||||||
acc[cur] = cur;
|
acc[cur] = cur;
|
||||||
|
|
|
@ -6,9 +6,8 @@ import { generateView } from '@inula/jsx-view-generator';
|
||||||
import { parseView } from '@inula/jsx-view-parser';
|
import { parseView } from '@inula/jsx-view-parser';
|
||||||
import { attributeMap, htmlTags, importMap } from './const';
|
import { attributeMap, htmlTags, importMap } from './const';
|
||||||
|
|
||||||
|
|
||||||
export class PluginProvider {
|
export class PluginProvider {
|
||||||
private inulaPackageName = 'inula-reactive';
|
private inulaPackageName = '@inula/no-vdom';
|
||||||
// ---- Plugin Level ----
|
// ---- Plugin Level ----
|
||||||
private readonly babelApi: typeof babel
|
private readonly babelApi: typeof babel
|
||||||
private readonly t: typeof t
|
private readonly t: typeof t
|
||||||
|
@ -47,6 +46,9 @@ export class PluginProvider {
|
||||||
|
|
||||||
private templateIdx = -1
|
private templateIdx = -1
|
||||||
|
|
||||||
|
// ---- record used apis for automatic import
|
||||||
|
private allUsedApis: Set<string> = new Set<string>();
|
||||||
|
|
||||||
programEnterVisitor(
|
programEnterVisitor(
|
||||||
path: NodePath<t.Program>,
|
path: NodePath<t.Program>,
|
||||||
filename: string | undefined
|
filename: string | undefined
|
||||||
|
@ -71,8 +73,19 @@ export class PluginProvider {
|
||||||
programExitVisitor(path: NodePath<t.Program>): void {
|
programExitVisitor(path: NodePath<t.Program>): void {
|
||||||
if (!this.fileEnter) return;
|
if (!this.fileEnter) return;
|
||||||
this.fileEnter = false;
|
this.fileEnter = false;
|
||||||
|
if (this.allUsedApis.size) {
|
||||||
|
this.programNode!.body.unshift(this.autoImport());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoImport(): t.ImportDeclaration {
|
||||||
|
const t = this.babelApi.types;
|
||||||
|
// add automatic import
|
||||||
|
return t.importDeclaration(
|
||||||
|
[...this.allUsedApis].map(api => t.importSpecifier(t.identifier(api), t.identifier(api))),
|
||||||
|
t.stringLiteral(this.inulaPackageName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
jsxElementVisitor(path: NodePath<t.JSXElement | t.JSXFragment>): void {
|
jsxElementVisitor(path: NodePath<t.JSXElement | t.JSXFragment>): void {
|
||||||
if (!this.fileEnter) return;
|
if (!this.fileEnter) return;
|
||||||
|
@ -81,7 +94,7 @@ export class PluginProvider {
|
||||||
htmlTags: this.htmlTags,
|
htmlTags: this.htmlTags,
|
||||||
parseTemplate: this.parseTemplate,
|
parseTemplate: this.parseTemplate,
|
||||||
});
|
});
|
||||||
const [templates, viewAst] = generateView(viewUnits, {
|
const [templates, viewAst, usedApis] = generateView(viewUnits, {
|
||||||
babelApi: this.babelApi,
|
babelApi: this.babelApi,
|
||||||
importMap,
|
importMap,
|
||||||
attributeMap: this.attributeMap,
|
attributeMap: this.attributeMap,
|
||||||
|
@ -89,7 +102,8 @@ export class PluginProvider {
|
||||||
this.templateIdx += templates.length;
|
this.templateIdx += templates.length;
|
||||||
// ---- Add templates to the program
|
// ---- Add templates to the program
|
||||||
this.programNode!.body.unshift(...templates);
|
this.programNode!.body.unshift(...templates);
|
||||||
|
// ---- collect the used apis
|
||||||
|
usedApis.forEach(api => this.allUsedApis.add(api))
|
||||||
// ---- Replace the JSXElement with the viewAst
|
// ---- Replace the JSXElement with the viewAst
|
||||||
path.replaceWith(viewAst);
|
path.replaceWith(viewAst);
|
||||||
path.skip();
|
path.skip();
|
||||||
|
|
|
@ -1,47 +1,51 @@
|
||||||
import { UnitProp, ViewUnit } from '@inula/jsx-view-parser';
|
import { UnitProp, ViewUnit } from '@inula/jsx-view-parser';
|
||||||
import { ViewGeneratorConfig } from '../types';
|
import { ViewGeneratorConfig, ViewGeneratorContext } from '../types';
|
||||||
import type { types as t, traverse } from '@babel/core';
|
import type { types as t, traverse } from '@babel/core';
|
||||||
|
|
||||||
|
|
||||||
export default class BaseGenerator {
|
export default class BaseGenerator {
|
||||||
readonly viewUnit: ViewUnit
|
readonly viewUnit: ViewUnit;
|
||||||
readonly config: ViewGeneratorConfig
|
readonly config: ViewGeneratorConfig;
|
||||||
readonly t
|
readonly t;
|
||||||
readonly traverse: typeof traverse
|
readonly traverse: typeof traverse;
|
||||||
readonly elementAttributeMap
|
readonly elementAttributeMap;
|
||||||
readonly importMap
|
readonly importMap;
|
||||||
|
readonly context : ViewGeneratorContext;
|
||||||
|
|
||||||
constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig) {
|
constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig, context: ViewGeneratorContext) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.t = config.babelApi.types;
|
this.t = config.babelApi.types;
|
||||||
this.traverse = config.babelApi.traverse;
|
this.traverse = config.babelApi.traverse;
|
||||||
this.importMap = config.importMap;
|
this.importMap = config.importMap;
|
||||||
this.viewUnit = viewUnit;
|
this.viewUnit = viewUnit;
|
||||||
this.elementAttributeMap = config.attributeMap
|
this.elementAttributeMap = config.attributeMap
|
||||||
? Object.entries(config.attributeMap).reduce<Record<string, string[]>>(
|
? Object.entries(config.attributeMap).reduce<Record<string, string[]>>((acc, [key, elements]) => {
|
||||||
(acc, [key, elements]) => {
|
elements.forEach(element => {
|
||||||
elements.forEach(element => {
|
if (!acc[element]) acc[element] = [];
|
||||||
if (!acc[element]) acc[element] = [];
|
acc[element].push(key);
|
||||||
acc[element].push(key);
|
});
|
||||||
});
|
return acc;
|
||||||
return acc;
|
}, {})
|
||||||
},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
: {};
|
: {};
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Init Statements
|
// ---- Init Statements
|
||||||
private readonly initStatements: t.Statement[] = []
|
private readonly initStatements: t.Statement[] = [];
|
||||||
|
|
||||||
addStatement(...statements: t.Statement[]) {
|
addStatement(...statements: t.Statement[]) {
|
||||||
this.initStatements.push(...statements);
|
this.initStatements.push(...statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly templates: t.Statement[] = []
|
private readonly templates: t.Statement[] = [];
|
||||||
|
|
||||||
addTemplate(...template: t.Statement[]) {
|
addTemplate(...template: t.Statement[]) {
|
||||||
this.templates.push(...template);
|
this.templates.push(...template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addUsedApi(apiName: string) {
|
||||||
|
this.context.collectApis(apiName);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Generate ----
|
// ---- Generate ----
|
||||||
/**
|
/**
|
||||||
* @brief To be implemented by the subclass
|
* @brief To be implemented by the subclass
|
||||||
|
@ -50,7 +54,7 @@ export default class BaseGenerator {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(): [string, t.Statement[], t.Statement[]]{
|
generate(): [string, t.Statement[], t.Statement[]] {
|
||||||
const nodeName = this.run();
|
const nodeName = this.run();
|
||||||
return [nodeName, this.initStatements, this.templates];
|
return [nodeName, this.initStatements, this.templates];
|
||||||
}
|
}
|
||||||
|
@ -72,7 +76,7 @@ export default class BaseGenerator {
|
||||||
reactive = true;
|
reactive = true;
|
||||||
path.stop();
|
path.stop();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return reactive;
|
return reactive;
|
||||||
}
|
}
|
||||||
|
@ -81,18 +85,20 @@ export default class BaseGenerator {
|
||||||
private readonly prefixMap = {
|
private readonly prefixMap = {
|
||||||
node: '$node',
|
node: '$node',
|
||||||
template: '$template',
|
template: '$template',
|
||||||
}
|
};
|
||||||
|
|
||||||
nodeIdx = -1;
|
nodeIdx = -1;
|
||||||
|
|
||||||
geneNodeName(idx?: number): string {
|
geneNodeName(idx?: number): string {
|
||||||
return `${this.prefixMap.node}${idx ?? ++this.nodeIdx}`;
|
return `${this.prefixMap.node}${idx ?? ++this.nodeIdx}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
templateIdx = -1;
|
templateIdx = -1;
|
||||||
|
|
||||||
generateTemplateName() {
|
generateTemplateName() {
|
||||||
return `${this.prefixMap.template}${++this.templateIdx}`;
|
return `${this.prefixMap.template}${++this.templateIdx}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---- Utils ----
|
// ---- Utils ----
|
||||||
/**
|
/**
|
||||||
* @brief Wrap the value in a file
|
* @brief Wrap the value in a file
|
||||||
|
@ -110,8 +116,9 @@ export default class BaseGenerator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCollector(): [t.Statement[], (statement: t.Statement | t.Statement[] | null) => void]{
|
createCollector(): [t.Statement[], (statement: t.Statement | t.Statement[] | null) => void] {
|
||||||
const statements: t.Statement[] = [];
|
const statements: t.Statement[] = [];
|
||||||
|
|
||||||
function collect(statement: t.Statement | t.Statement[] | null) {
|
function collect(statement: t.Statement | t.Statement[] | null) {
|
||||||
if (Array.isArray(statement)) {
|
if (Array.isArray(statement)) {
|
||||||
statements.push(...statement);
|
statements.push(...statement);
|
||||||
|
@ -119,10 +126,18 @@ export default class BaseGenerator {
|
||||||
statements.push(statement);
|
statements.push(statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [statements, collect];
|
return [statements, collect];
|
||||||
}
|
}
|
||||||
|
|
||||||
parseViewProp(prop: UnitProp, generateView: (units: ViewUnit[], config: ViewGeneratorConfig, templateIdx: number) => [t.Statement[], t.ExpressionStatement]): t.Expression {
|
parseViewProp(
|
||||||
|
prop: UnitProp,
|
||||||
|
generateView: (
|
||||||
|
units: ViewUnit[],
|
||||||
|
config: ViewGeneratorConfig,
|
||||||
|
templateIdx: number
|
||||||
|
) => [t.Statement[], t.ExpressionStatement, string[]]
|
||||||
|
): t.Expression {
|
||||||
let value = prop.value;
|
let value = prop.value;
|
||||||
const viewPropMap = prop.viewPropMap;
|
const viewPropMap = prop.viewPropMap;
|
||||||
const propNodeMap = Object.fromEntries(
|
const propNodeMap = Object.fromEntries(
|
||||||
|
@ -142,13 +157,16 @@ export default class BaseGenerator {
|
||||||
}
|
}
|
||||||
path.replaceWith(propNodeMap[key]);
|
path.replaceWith(propNodeMap[key]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseProps(props: Record<string, UnitProp>, generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [t.Statement[], t.ExpressionStatement]) {
|
parseProps(
|
||||||
|
props: Record<string, UnitProp>,
|
||||||
|
generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [t.Statement[], t.ExpressionStatement, string[]]
|
||||||
|
) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(props).map(([key, prop]) => {
|
Object.entries(props).map(([key, prop]) => {
|
||||||
return [key, this.parseViewProp(prop, generateView)];
|
return [key, this.parseViewProp(prop, generateView)];
|
||||||
|
|
|
@ -48,6 +48,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
nodeName: string,
|
nodeName: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.setStyle);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.setStyle),
|
this.t.identifier(this.importMap.setStyle),
|
||||||
[this.t.identifier(nodeName), value]
|
[this.t.identifier(nodeName), value]
|
||||||
|
@ -62,6 +63,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
nodeName: string,
|
nodeName: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.setDataset);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.setDataset),
|
this.t.identifier(this.importMap.setDataset),
|
||||||
[this.t.identifier(nodeName), value]
|
[this.t.identifier(nodeName), value]
|
||||||
|
@ -96,6 +98,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
key: string,
|
key: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.setProperty);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.setProperty),
|
this.t.identifier(this.importMap.setProperty),
|
||||||
[this.t.identifier(nodeName), this.t.stringLiteral(key), value]
|
[this.t.identifier(nodeName), this.t.stringLiteral(key), value]
|
||||||
|
@ -130,6 +133,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
key: string,
|
key: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.setAttribute);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.setAttribute),
|
this.t.identifier(this.importMap.setAttribute),
|
||||||
[this.t.identifier(nodeName), this.t.stringLiteral(key), value]
|
[this.t.identifier(nodeName), this.t.stringLiteral(key), value]
|
||||||
|
@ -145,6 +149,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
eventName: string,
|
eventName: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.delegateEvent);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.delegateEvent),
|
this.t.identifier(this.importMap.delegateEvent),
|
||||||
[this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]
|
[this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]
|
||||||
|
@ -178,6 +183,7 @@ export class HTMLPropGenerator extends BaseGenerator {
|
||||||
eventName: string,
|
eventName: string,
|
||||||
value: t.Expression,
|
value: t.Expression,
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.addEventListener);
|
||||||
return this.t.callExpression(
|
return this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.addEventListener),
|
this.t.identifier(this.importMap.addEventListener),
|
||||||
[this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]
|
[this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class CompGenerator extends BaseGenerator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @View
|
* @View
|
||||||
* const $el = createComponent(tag, {
|
* const $el = runComponent(tag, {
|
||||||
* ...props
|
* ...props
|
||||||
* }, spreadProps)
|
* }, spreadProps)
|
||||||
*/
|
*/
|
||||||
|
@ -47,7 +47,7 @@ export class CompGenerator extends BaseGenerator {
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
const statement = generateBlock(children, this.config);
|
const statement = generateBlock(children, this.config, this.context);
|
||||||
propNode.properties.push(
|
propNode.properties.push(
|
||||||
this.t.objectMethod('get', this.t.identifier('children'), [], statement)
|
this.t.objectMethod('get', this.t.identifier('children'), [], statement)
|
||||||
);
|
);
|
||||||
|
@ -55,12 +55,12 @@ export class CompGenerator extends BaseGenerator {
|
||||||
nodes.push(propNode);
|
nodes.push(propNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addUsedApi(this.importMap.runComponent);
|
||||||
return [name, this.t.variableDeclaration('const', [
|
return [name, this.t.variableDeclaration('const', [
|
||||||
this.t.variableDeclarator(
|
this.t.variableDeclarator(
|
||||||
this.t.identifier(name),
|
this.t.identifier(name),
|
||||||
this.t.callExpression(
|
this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.createComponent),
|
this.t.identifier(this.importMap.runComponent),
|
||||||
[tag, ...nodes]
|
[tag, ...nodes]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,11 +34,12 @@ export class HTMLGenerator extends HTMLPropGenerator {
|
||||||
*/
|
*/
|
||||||
declareHTMLNode(tag: t.Expression): [string, t.Statement] {
|
declareHTMLNode(tag: t.Expression): [string, t.Statement] {
|
||||||
const name = this.geneNodeName();
|
const name = this.geneNodeName();
|
||||||
|
this.addUsedApi(this.importMap.createElement);
|
||||||
return [name, this.t.variableDeclaration('const', [
|
return [name, this.t.variableDeclaration('const', [
|
||||||
this.t.variableDeclarator(
|
this.t.variableDeclarator(
|
||||||
this.t.identifier(name),
|
this.t.identifier(name),
|
||||||
this.t.callExpression(
|
this.t.callExpression(
|
||||||
this.t.identifier('createElement'),
|
this.t.identifier(this.importMap.createElement),
|
||||||
[tag]
|
[tag]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -53,6 +54,7 @@ export class HTMLGenerator extends HTMLPropGenerator {
|
||||||
parent: string,
|
parent: string,
|
||||||
child: string
|
child: string
|
||||||
) {
|
) {
|
||||||
|
this.addUsedApi(this.importMap.insert);
|
||||||
return this.t.expressionStatement(
|
return this.t.expressionStatement(
|
||||||
this.t.callExpression(
|
this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.insert),
|
this.t.identifier(this.importMap.insert),
|
||||||
|
|
|
@ -115,6 +115,7 @@ export class TemplateGenerator extends HTMLPropGenerator{
|
||||||
nextName: string
|
nextName: string
|
||||||
) {
|
) {
|
||||||
const nextNode = nextName ? [this.t.identifier(nextName)] : [];
|
const nextNode = nextName ? [this.t.identifier(nextName)] : [];
|
||||||
|
this.addUsedApi(this.importMap.insert);
|
||||||
return this.t.expressionStatement(
|
return this.t.expressionStatement(
|
||||||
this.t.callExpression(
|
this.t.callExpression(
|
||||||
this.t.identifier(this.importMap.insert),
|
this.t.identifier(this.importMap.insert),
|
||||||
|
|
|
@ -12,6 +12,7 @@ export class TextGenerator extends BaseGenerator {
|
||||||
|
|
||||||
declareTextNode(content: t.Literal): [string, t.Statement] {
|
declareTextNode(content: t.Literal): [string, t.Statement] {
|
||||||
const name = this.geneNodeName();
|
const name = this.geneNodeName();
|
||||||
|
this.addUsedApi(this.importMap.createText);
|
||||||
return [name, this.t.variableDeclaration('const', [
|
return [name, this.t.variableDeclaration('const', [
|
||||||
this.t.variableDeclarator(
|
this.t.variableDeclarator(
|
||||||
this.t.identifier(name),
|
this.t.identifier(name),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ViewUnit } from '@inula/jsx-view-parser';
|
import { ViewUnit } from '@inula/jsx-view-parser';
|
||||||
import { CompGenerator } from './NodeGenerators/CompGenerator';
|
import { CompGenerator } from './NodeGenerators/CompGenerator';
|
||||||
import { HTMLGenerator } from './NodeGenerators/HTMLGenerator';
|
import { HTMLGenerator } from './NodeGenerators/HTMLGenerator';
|
||||||
import { ViewGeneratorConfig } from './types';
|
import { ViewGeneratorConfig, ViewGeneratorContext } from './types';
|
||||||
import type { types as t } from '@babel/core';
|
import type { types as t } from '@babel/core';
|
||||||
import { TemplateGenerator } from './NodeGenerators/TemplateGenerator';
|
import { TemplateGenerator } from './NodeGenerators/TemplateGenerator';
|
||||||
import { TextGenerator } from './NodeGenerators/TextGenerator';
|
import { TextGenerator } from './NodeGenerators/TextGenerator';
|
||||||
|
@ -17,38 +17,54 @@ export const viewGeneratorMap = {
|
||||||
env: CompGenerator,
|
env: CompGenerator,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export function generateNew(
|
||||||
export function generateNew(oldGenerator: any, viewUnit: ViewUnit, resetIdx = true): [string, t.Statement[], t.Statement[]]{
|
oldGenerator: any,
|
||||||
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, oldGenerator.config);
|
viewUnit: ViewUnit,
|
||||||
|
resetIdx = true
|
||||||
|
): [string, t.Statement[], t.Statement[]] {
|
||||||
|
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, oldGenerator.config, oldGenerator.context);
|
||||||
if (resetIdx) generator.nodeIdx = oldGenerator.nodeIdx;
|
if (resetIdx) generator.nodeIdx = oldGenerator.nodeIdx;
|
||||||
const [name, statements, templates] = generator.generate();
|
const [name, statements, templates] = generator.generate();
|
||||||
if (resetIdx) oldGenerator.nodeIdx = generator.nodeIdx;
|
if (resetIdx) oldGenerator.nodeIdx = generator.nodeIdx;
|
||||||
return [name, statements, templates];
|
return [name, statements, templates];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateBlock(viewUnits: ViewUnit[], config: ViewGeneratorConfig) {
|
export function generateBlock(viewUnits: ViewUnit[], config: ViewGeneratorConfig, context: ViewGeneratorContext) {
|
||||||
const t = config.babelApi.types;
|
const t = config.babelApi.types;
|
||||||
const names: string[] = [];
|
const names: string[] = [];
|
||||||
const statements = viewUnits.flatMap(viewUnit => {
|
const statements = viewUnits.flatMap(viewUnit => {
|
||||||
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);
|
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config, context);
|
||||||
const [name, statements] = generator.generate();
|
const [name, statements] = generator.generate();
|
||||||
names.push(name);
|
names.push(name);
|
||||||
return statements;
|
return statements;
|
||||||
});
|
});
|
||||||
|
|
||||||
const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));
|
const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));
|
||||||
return (
|
return t.blockStatement(statements.concat(returnStatement));
|
||||||
t.blockStatement(statements.concat(returnStatement))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig, templateIdx=-1): [t.Statement[], t.ExpressionStatement] {
|
function apisCollect() {
|
||||||
|
const usedApis = new Set<string>();
|
||||||
|
return {
|
||||||
|
collectApis: (name: string) => usedApis.add(name),
|
||||||
|
getUsedApis: () => [...usedApis],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateView(
|
||||||
|
viewUnits: ViewUnit[],
|
||||||
|
config: ViewGeneratorConfig,
|
||||||
|
templateIdx = -1
|
||||||
|
): [t.Statement[], t.ExpressionStatement, string[]] {
|
||||||
const t = config.babelApi.types;
|
const t = config.babelApi.types;
|
||||||
const names: string[] = [];
|
const names: string[] = [];
|
||||||
const allTemplates: t.Statement[] = [];
|
const allTemplates: t.Statement[] = [];
|
||||||
let nodeIdx = -1;
|
let nodeIdx = -1;
|
||||||
|
const { collectApis, getUsedApis } = apisCollect();
|
||||||
const statements = viewUnits.flatMap(viewUnit => {
|
const statements = viewUnits.flatMap(viewUnit => {
|
||||||
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);
|
const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config, {
|
||||||
|
collectApis
|
||||||
|
});
|
||||||
generator.templateIdx = templateIdx;
|
generator.templateIdx = templateIdx;
|
||||||
generator.nodeIdx = nodeIdx;
|
generator.nodeIdx = nodeIdx;
|
||||||
const [name, statements, templates] = generator.generate();
|
const [name, statements, templates] = generator.generate();
|
||||||
|
@ -56,17 +72,17 @@ export function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig,
|
||||||
nodeIdx = generator.nodeIdx;
|
nodeIdx = generator.nodeIdx;
|
||||||
names.push(name);
|
names.push(name);
|
||||||
allTemplates.push(...templates);
|
allTemplates.push(...templates);
|
||||||
|
// merge all imports
|
||||||
return statements;
|
return statements;
|
||||||
});
|
});
|
||||||
|
|
||||||
const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));
|
const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));
|
||||||
|
|
||||||
return [allTemplates, (
|
return [
|
||||||
|
allTemplates,
|
||||||
t.expressionStatement(
|
t.expressionStatement(
|
||||||
t.callExpression(
|
t.callExpression(t.arrowFunctionExpression([], t.blockStatement(statements.concat(returnStatement))), [])
|
||||||
t.arrowFunctionExpression([], t.blockStatement(statements.concat(returnStatement))),
|
),
|
||||||
[]
|
[...getUsedApis()],
|
||||||
)
|
];
|
||||||
)
|
|
||||||
)];
|
|
||||||
}
|
}
|
|
@ -1,49 +1,61 @@
|
||||||
import { describe, it } from 'vitest';
|
import { describe, it } from 'vitest';
|
||||||
import { expectView } from './mock';
|
import { expectView } from './mock';
|
||||||
|
|
||||||
|
|
||||||
describe('Comp', () => {
|
describe('Comp', () => {
|
||||||
it('should generate a Component', () => {
|
it('should generate a Component', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
|
/*jsx*/ `
|
||||||
<Comp/>
|
<Comp/>
|
||||||
`, /*js*/ `
|
`,
|
||||||
const $node0 = createComponent(Comp)
|
/*js*/ `
|
||||||
`);
|
const $node0 = runComponent(Comp)
|
||||||
|
`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a Component with props', () => {
|
it('should generate a Component with props', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
|
/*jsx*/ `
|
||||||
<Comp prop1="value1" prop2={value2}/>
|
<Comp prop1="value1" prop2={value2}/>
|
||||||
`, /*js*/`
|
`,
|
||||||
const $node0 = createComponent(Comp, {
|
/*js*/ `
|
||||||
|
const $node0 = runComponent(Comp, {
|
||||||
prop1: "value1",
|
prop1: "value1",
|
||||||
prop2: value2
|
prop2: value2
|
||||||
})
|
})
|
||||||
`);
|
`,
|
||||||
|
[],
|
||||||
|
/*apis*/ ['runComponent']
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a Component with children', () => {
|
it('should generate a Component with children', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
|
/*jsx*/ `
|
||||||
<Comp>
|
<Comp>
|
||||||
<div></div>
|
<div></div>
|
||||||
</Comp>
|
</Comp>
|
||||||
`, /*js*/`
|
`,
|
||||||
const $node0 = createComponent(Comp, {
|
/*js*/ `
|
||||||
|
const $node0 = runComponent(Comp, {
|
||||||
get children() {
|
get children() {
|
||||||
const $node0 = createElement("div")
|
const $node0 = createElement("div")
|
||||||
return [$node0]
|
return [$node0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a Component with props and children', () => {
|
it('should generate a Component with props and children', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
|
/*jsx*/ `
|
||||||
<Comp prop1="value1" prop2={value2}>
|
<Comp prop1="value1" prop2={value2}>
|
||||||
<div></div>
|
<div></div>
|
||||||
</Comp>
|
</Comp>
|
||||||
`, /*js*/`
|
`,
|
||||||
const $node0 = createComponent(Comp, {
|
/*js*/ `
|
||||||
|
const $node0 = runComponent(Comp, {
|
||||||
prop1: "value1",
|
prop1: "value1",
|
||||||
prop2: value2,
|
prop2: value2,
|
||||||
get children() {
|
get children() {
|
||||||
|
@ -51,32 +63,41 @@ describe('Comp', () => {
|
||||||
return [$node0]
|
return [$node0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a Component with reactive props', () => {
|
it('should generate a Component with reactive props', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
|
/*jsx*/ `
|
||||||
<Comp prop1={value.get()}/>
|
<Comp prop1={value.get()}/>
|
||||||
`, /*js*/`
|
`,
|
||||||
const $node0 = createComponent(Comp, {
|
/*js*/ `
|
||||||
|
const $node0 = runComponent(Comp, {
|
||||||
get prop1() {
|
get prop1() {
|
||||||
return value.get()
|
return value.get()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a Component with render/view props', () => {
|
it('should generate a Component with render/view props', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(
|
||||||
<Comp render={<div>ok</div>}/>
|
/*jsx*/ `
|
||||||
`, /*js*/`
|
<Dynamic component="h1">foo</Dynamic>;
|
||||||
const $node0 = createComponent(Comp, {
|
`,
|
||||||
render: (() => {
|
/*js*/ `
|
||||||
const $node0 = createElement("div")
|
const $node0 = runComponent(Dynamic, {
|
||||||
$node0.textContent = "ok"
|
component: "h1",
|
||||||
return [$node0]
|
get children() {
|
||||||
})()
|
const $node0 = createText("foo");
|
||||||
})
|
return [$node0];
|
||||||
`);
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
[],
|
||||||
|
['createText', 'runComponent']
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const importMap = [
|
||||||
'addEventListener',
|
'addEventListener',
|
||||||
'watch',
|
'watch',
|
||||||
'insert',
|
'insert',
|
||||||
'createComponent',
|
'runComponent',
|
||||||
'createText'
|
'createText'
|
||||||
].reduce<Record<string, string>>((acc, cur) => {
|
].reduce<Record<string, string>>((acc, cur) => {
|
||||||
acc[cur] = cur;
|
acc[cur] = cur;
|
||||||
|
|
|
@ -26,7 +26,11 @@ describe('Expression', () => {
|
||||||
insert($node0, $node2);
|
insert($node0, $node2);
|
||||||
const $node3 = createText("222");
|
const $node3 = createText("222");
|
||||||
insert($node0, $node3);
|
insert($node0, $node3);
|
||||||
`
|
`, [], [
|
||||||
|
"createElement",
|
||||||
|
"createText",
|
||||||
|
"insert",
|
||||||
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,7 +47,7 @@ describe('Expression', () => {
|
||||||
insert($node1, $node3, $node2);
|
insert($node1, $node3, $node2);
|
||||||
`
|
`
|
||||||
, [
|
, [
|
||||||
`const $template0 = () => {
|
`const $template0 = (() => {
|
||||||
const $node0 = createElement("div");
|
const $node0 = createElement("div");
|
||||||
const $node1 = createElement("div");
|
const $node1 = createElement("div");
|
||||||
const $node2 = createText("111");
|
const $node2 = createText("111");
|
||||||
|
@ -52,7 +56,7 @@ describe('Expression', () => {
|
||||||
insert($node1, $node3);
|
insert($node1, $node3);
|
||||||
insert($node0, $node1);
|
insert($node0, $node1);
|
||||||
return $node0;
|
return $node0;
|
||||||
};`
|
})();`
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,10 +58,11 @@ describe('HTML', () => {
|
||||||
|
|
||||||
it('should generate a div element with a static event', () => {
|
it('should generate a div element with a static event', () => {
|
||||||
expectView(/*jsx*/`
|
expectView(/*jsx*/`
|
||||||
<div onClick={myFunction}></div>
|
<div onClick={myFunction} onMouseDown={otherFn}></div>
|
||||||
`, /*js*/`
|
`, /*js*/`
|
||||||
const $node0 = createElement("div")
|
const $node0 = createElement("div")
|
||||||
delegateEvent($node0, "click", myFunction)
|
delegateEvent($node0, "click", myFunction)
|
||||||
|
delegateEvent($node0, "mousedown", otherFn)
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,7 +122,7 @@ describe('HTML', () => {
|
||||||
</div>
|
</div>
|
||||||
`, /*js*/`
|
`, /*js*/`
|
||||||
const $node0 = createElement("div")
|
const $node0 = createElement("div")
|
||||||
const $node1 = createComponent(Comp)
|
const $node1 = runComponent(Comp)
|
||||||
insert($node0, $node1)
|
insert($node0, $node1)
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,8 +36,8 @@ function formatCode(code: string) {
|
||||||
)!.code;
|
)!.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expectView(code: string, expected: string, expectedTemplates?: string[]) {
|
export function expectView(code: string, expected: string, expectedTemplates?: string[], expectApis?: string[]) {
|
||||||
const [templates, viewAst] = generateView(code);
|
const [templates, viewAst, apis] = generateView(code);
|
||||||
const statements = (((viewAst.expression as t.CallExpression)
|
const statements = (((viewAst.expression as t.CallExpression)
|
||||||
.callee as t.ArrowFunctionExpression)
|
.callee as t.ArrowFunctionExpression)
|
||||||
.body as t.BlockStatement)
|
.body as t.BlockStatement)
|
||||||
|
@ -53,7 +53,9 @@ export function expectView(code: string, expected: string, expectedTemplates?: s
|
||||||
const templateCode = templates.map(template => generate(template).code);
|
const templateCode = templates.map(template => generate(template).code);
|
||||||
expectedTemplates = expectedTemplates.map(formatCode);
|
expectedTemplates = expectedTemplates.map(formatCode);
|
||||||
expect(templateCode).toEqual(expectedTemplates.map(formatCode));
|
expect(templateCode).toEqual(expectedTemplates.map(formatCode));
|
||||||
|
}
|
||||||
|
if (expectApis) {
|
||||||
|
expect(expectApis).toEqual(apis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ describe('Template', () => {
|
||||||
</div>
|
</div>
|
||||||
`, /*js*/`
|
`, /*js*/`
|
||||||
const $node0 = $template0.cloneNode(true)
|
const $node0 = $template0.cloneNode(true)
|
||||||
const $node1 = createComponent(Comp)
|
const $node1 = runComponent(Comp)
|
||||||
insert($node0, $node1)
|
insert($node0, $node1)
|
||||||
`, [
|
`, [
|
||||||
/*js*/`
|
/*js*/`
|
||||||
|
@ -182,7 +182,7 @@ describe('Template', () => {
|
||||||
`, /*js*/`
|
`, /*js*/`
|
||||||
const $node0 = $template0.cloneNode(true)
|
const $node0 = $template0.cloneNode(true)
|
||||||
const $node1 = $node0.firstChild.nextSibling;
|
const $node1 = $node0.firstChild.nextSibling;
|
||||||
const $node2 = createComponent(Comp);
|
const $node2 = runComponent(Comp);
|
||||||
insert($node0, $node2, $node1);
|
insert($node0, $node2, $node1);
|
||||||
`, [
|
`, [
|
||||||
/*js*/`
|
/*js*/`
|
||||||
|
@ -238,7 +238,7 @@ describe('Template', () => {
|
||||||
});
|
});
|
||||||
$node3.className = cls;
|
$node3.className = cls;
|
||||||
watch(() => setProperty($node4, "id", id.get()));
|
watch(() => setProperty($node4, "id", id.get()));
|
||||||
const $node5 = createComponent(Comp, {
|
const $node5 = runComponent(Comp, {
|
||||||
myProp: prop,
|
myProp: prop,
|
||||||
get reactiveProp() {
|
get reactiveProp() {
|
||||||
return prop.get();
|
return prop.get();
|
||||||
|
@ -249,7 +249,7 @@ describe('Template', () => {
|
||||||
const $node7 = $node6.firstChild.nextSibling;
|
const $node7 = $node6.firstChild.nextSibling;
|
||||||
const $node8 = $node7.firstChild.nextSibling;
|
const $node8 = $node7.firstChild.nextSibling;
|
||||||
watch(() => setProperty($node8, "id", id.get()));
|
watch(() => setProperty($node8, "id", id.get()));
|
||||||
const $node9 = createComponent(Comp);
|
const $node9 = runComponent(Comp);
|
||||||
insert($node6, $node9, $node7);
|
insert($node6, $node9, $node7);
|
||||||
`, [
|
`, [
|
||||||
/*js*/`
|
/*js*/`
|
||||||
|
|
|
@ -12,3 +12,8 @@ export interface ViewGeneratorConfig {
|
||||||
*/
|
*/
|
||||||
attributeMap?: Record<string, string[]>
|
attributeMap?: Record<string, string[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CollectApis = (name: string) => void;
|
||||||
|
export interface ViewGeneratorContext {
|
||||||
|
collectApis: CollectApis;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue