perf(#10923): support multiple components
This commit is contained in:
parent
172f301df4
commit
5e2bf549ac
|
@ -3,7 +3,7 @@ import { generate } from 'astring';
|
|||
import { expect, it } from 'vitest';
|
||||
import { unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name';
|
||||
|
||||
it('Composition API', () => {
|
||||
it('Composition API (standard)', () => {
|
||||
const ast = parse(`
|
||||
import { c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
|
||||
|
@ -214,3 +214,336 @@ const index_photos = _sfc_main;
|
|||
export {index_photos as default};
|
||||
`.slice(1));
|
||||
});
|
||||
|
||||
it('Composition API (with `useCssModule()`)', () => {
|
||||
const ast = parse(`
|
||||
import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
|
||||
import { d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
|
||||
function isDebuggerEnabled(id) {
|
||||
try {
|
||||
return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function stackTraceInstances() {
|
||||
let instance = getCurrentInstance();
|
||||
const stack = [];
|
||||
while (instance) {
|
||||
stack.push(instance);
|
||||
instance = instance.parent;
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
const _sfc_main = defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "down"
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
noGap: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
ad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props, { slots, expose }) {
|
||||
const $style = useCssModule();
|
||||
function getDateText(time) {
|
||||
const date = new Date(time).getDate();
|
||||
const month = new Date(time).getMonth() + 1;
|
||||
return i18n.t("monthAndDay", {
|
||||
month: month.toString(),
|
||||
day: date.toString()
|
||||
});
|
||||
}
|
||||
if (props.items.length === 0)
|
||||
return;
|
||||
const renderChildrenImpl = () => props.items.map((item, i) => {
|
||||
if (!slots || !slots.default)
|
||||
return;
|
||||
const el = slots.default({
|
||||
item
|
||||
})[0];
|
||||
if (el.key == null && item.id)
|
||||
el.key = item.id;
|
||||
if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) {
|
||||
const separator = h("div", {
|
||||
class: $style["separator"],
|
||||
key: item.id + ":separator"
|
||||
}, h("p", {
|
||||
class: $style["date"]
|
||||
}, [
|
||||
h("span", {
|
||||
class: $style["date-1"]
|
||||
}, [
|
||||
h("i", {
|
||||
class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
|
||||
}),
|
||||
getDateText(item.createdAt)
|
||||
]),
|
||||
h("span", {
|
||||
class: $style["date-2"]
|
||||
}, [
|
||||
getDateText(props.items[i + 1].createdAt),
|
||||
h("i", {
|
||||
class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
|
||||
})
|
||||
])
|
||||
]));
|
||||
return [el, separator];
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
key: item.id + ":ad",
|
||||
prefer: ["horizontal", "horizontal-big"]
|
||||
}), el];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
});
|
||||
const renderChildren = () => {
|
||||
const children = renderChildrenImpl();
|
||||
if (isDebuggerEnabled(6864)) {
|
||||
const nodes = children.flatMap((node) => node ?? []);
|
||||
const keys = new Set(nodes.map((node) => node.key));
|
||||
if (keys.size !== nodes.length) {
|
||||
const id = crypto.randomUUID();
|
||||
const instances = stackTraceInstances();
|
||||
toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`));
|
||||
console.warn({ id, debugId: 6864, stack: instances });
|
||||
}
|
||||
}
|
||||
return children;
|
||||
};
|
||||
function onBeforeLeave(el) {
|
||||
el.style.top = \`\${el.offsetTop}px\`;
|
||||
el.style.left = \`\${el.offsetLeft}px\`;
|
||||
}
|
||||
function onLeaveCanceled(el) {
|
||||
el.style.top = "";
|
||||
el.style.left = "";
|
||||
}
|
||||
return () => h(
|
||||
defaultStore.state.animation ? TransitionGroup : "div",
|
||||
{
|
||||
class: {
|
||||
[$style["date-separated-list"]]: true,
|
||||
[$style["date-separated-list-nogap"]]: props.noGap,
|
||||
[$style["reversed"]]: props.reversed,
|
||||
[$style["direction-down"]]: props.direction === "down",
|
||||
[$style["direction-up"]]: props.direction === "up"
|
||||
},
|
||||
...defaultStore.state.animation ? {
|
||||
name: "list",
|
||||
tag: "div",
|
||||
onBeforeLeave,
|
||||
onLeaveCanceled
|
||||
} : {}
|
||||
},
|
||||
{ default: renderChildren }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const reversed = "xxiZh";
|
||||
const separator = "xxeDx";
|
||||
const date = "xxawD";
|
||||
const style0 = {
|
||||
"date-separated-list": "xfKPa",
|
||||
"date-separated-list-nogap": "xf9zr",
|
||||
"direction-up": "x7AeO",
|
||||
"direction-down": "xBIqc",
|
||||
reversed: reversed,
|
||||
separator: separator,
|
||||
date: date,
|
||||
"date-1": "xwtmh",
|
||||
"date-1-icon": "xsNPa",
|
||||
"date-2": "x1xvw",
|
||||
"date-2-icon": "x9ZiG"
|
||||
};
|
||||
|
||||
const cssModules = {
|
||||
"$style": style0
|
||||
};
|
||||
const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
|
||||
|
||||
export { MkDateSeparatedList as M };
|
||||
`.slice(1), { sourceType: 'module' });
|
||||
unwindCssModuleClassName(ast);
|
||||
expect(generate(ast)).toBe(`
|
||||
import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js';
|
||||
import {d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
|
||||
function isDebuggerEnabled(id) {
|
||||
try {
|
||||
return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function stackTraceInstances() {
|
||||
let instance = getCurrentInstance();
|
||||
const stack = [];
|
||||
while (instance) {
|
||||
stack.push(instance);
|
||||
instance = instance.parent;
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
const _sfc_main = defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "down"
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
noGap: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
ad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props, {slots, expose}) {
|
||||
const $style = useCssModule();
|
||||
function getDateText(time) {
|
||||
const date = new Date(time).getDate();
|
||||
const month = new Date(time).getMonth() + 1;
|
||||
return i18n.t("monthAndDay", {
|
||||
month: month.toString(),
|
||||
day: date.toString()
|
||||
});
|
||||
}
|
||||
if (props.items.length === 0) return;
|
||||
const renderChildrenImpl = () => props.items.map((item, i) => {
|
||||
if (!slots || !slots.default) return;
|
||||
const el = slots.default({
|
||||
item
|
||||
})[0];
|
||||
if (el.key == null && item.id) el.key = item.id;
|
||||
if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) {
|
||||
const separator = h("div", {
|
||||
class: $style["separator"],
|
||||
key: item.id + ":separator"
|
||||
}, h("p", {
|
||||
class: $style["date"]
|
||||
}, [h("span", {
|
||||
class: $style["date-1"]
|
||||
}, [h("i", {
|
||||
class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
|
||||
}), getDateText(item.createdAt)]), h("span", {
|
||||
class: $style["date-2"]
|
||||
}, [getDateText(props.items[i + 1].createdAt), h("i", {
|
||||
class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
|
||||
})])]));
|
||||
return [el, separator];
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
key: item.id + ":ad",
|
||||
prefer: ["horizontal", "horizontal-big"]
|
||||
}), el];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
});
|
||||
const renderChildren = () => {
|
||||
const children = renderChildrenImpl();
|
||||
if (isDebuggerEnabled(6864)) {
|
||||
const nodes = children.flatMap(node => node ?? []);
|
||||
const keys = new Set(nodes.map(node => node.key));
|
||||
if (keys.size !== nodes.length) {
|
||||
const id = crypto.randomUUID();
|
||||
const instances = stackTraceInstances();
|
||||
toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`));
|
||||
console.warn({
|
||||
id,
|
||||
debugId: 6864,
|
||||
stack: instances
|
||||
});
|
||||
}
|
||||
}
|
||||
return children;
|
||||
};
|
||||
function onBeforeLeave(el) {
|
||||
el.style.top = \`\${el.offsetTop}px\`;
|
||||
el.style.left = \`\${el.offsetLeft}px\`;
|
||||
}
|
||||
function onLeaveCanceled(el) {
|
||||
el.style.top = "";
|
||||
el.style.left = "";
|
||||
}
|
||||
return () => h(defaultStore.state.animation ? TransitionGroup : "div", {
|
||||
class: {
|
||||
[$style["date-separated-list"]]: true,
|
||||
[$style["date-separated-list-nogap"]]: props.noGap,
|
||||
[$style["reversed"]]: props.reversed,
|
||||
[$style["direction-down"]]: props.direction === "down",
|
||||
[$style["direction-up"]]: props.direction === "up"
|
||||
},
|
||||
...defaultStore.state.animation ? {
|
||||
name: "list",
|
||||
tag: "div",
|
||||
onBeforeLeave,
|
||||
onLeaveCanceled
|
||||
} : {}
|
||||
}, {
|
||||
default: renderChildren
|
||||
});
|
||||
}
|
||||
});
|
||||
const reversed = "xxiZh";
|
||||
const separator = "xxeDx";
|
||||
const date = "xxawD";
|
||||
const style0 = {
|
||||
"date-separated-list": "xfKPa",
|
||||
"date-separated-list-nogap": "xf9zr",
|
||||
"direction-up": "x7AeO",
|
||||
"direction-down": "xBIqc",
|
||||
reversed: reversed,
|
||||
separator: separator,
|
||||
date: date,
|
||||
"date-1": "xwtmh",
|
||||
"date-1-icon": "xsNPa",
|
||||
"date-2": "x1xvw",
|
||||
"date-2-icon": "x9ZiG"
|
||||
};
|
||||
const cssModules = {
|
||||
"$style": style0
|
||||
};
|
||||
const MkDateSeparatedList = _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
|
||||
export {MkDateSeparatedList as M};
|
||||
`.slice(1));
|
||||
});
|
||||
|
|
|
@ -7,49 +7,25 @@ import type { Plugin } from 'vite';
|
|||
export function unwindCssModuleClassName(ast: estree.Node): void {
|
||||
(walk as typeof estreeWalker.walk)(ast, {
|
||||
enter(node, parent): void {
|
||||
// FIXME: support multiple exports
|
||||
if (node.type !== 'ExportNamedDeclaration') return;
|
||||
if (node.specifiers.length !== 1) return;
|
||||
if (node.specifiers[0].local.name === '_sfc_main') return;
|
||||
if (node.specifiers[0].exported.name !== 'default') return;
|
||||
if (parent?.type !== 'Program') return;
|
||||
const endIndex = parent.body.indexOf(node);
|
||||
const previousNode = parent.body[endIndex - 1];
|
||||
if (previousNode.type !== 'VariableDeclaration') return;
|
||||
if (previousNode.declarations.length !== 1) return;
|
||||
if (previousNode.declarations[0].id.type !== 'Identifier') return;
|
||||
if (previousNode.declarations[0].id.name !== node.specifiers[0].local.name) return;
|
||||
if (previousNode.declarations[0].init?.type !== 'CallExpression') return;
|
||||
if (previousNode.declarations[0].init.callee.type !== 'Identifier') return;
|
||||
if (previousNode.declarations[0].init.callee.name !== '_export_sfc') return;
|
||||
if (previousNode.declarations[0].init.arguments.length !== 2) return;
|
||||
if (previousNode.declarations[0].init.arguments[0].type !== 'Identifier') return;
|
||||
if (previousNode.declarations[0].init.arguments[0].name !== '_sfc_main') return;
|
||||
if (previousNode.declarations[0].init.arguments[1].type !== 'ArrayExpression') return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements.length !== 1) return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements[0]?.type !== 'ArrayExpression') return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements[0].elements.length !== 2) return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements[0].elements[0]?.type !== 'Literal') return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements[0].elements[0].value !== '__cssModules') return;
|
||||
if (previousNode.declarations[0].init.arguments[1].elements[0].elements[1]?.type !== 'Identifier') return;
|
||||
const cssModuleForestName = previousNode.declarations[0].init.arguments[1].elements[0].elements[1].name;
|
||||
parent.body[endIndex - 1] = {
|
||||
type: 'VariableDeclaration',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
name: node.specifiers[0].local.name,
|
||||
},
|
||||
init: {
|
||||
type: 'Identifier',
|
||||
name: '_sfc_main',
|
||||
},
|
||||
},
|
||||
],
|
||||
kind: 'const',
|
||||
};
|
||||
if (node.type !== 'VariableDeclaration') return;
|
||||
if (node.declarations.length !== 1) return;
|
||||
if (node.declarations[0].id.type !== 'Identifier') return;
|
||||
if (node.declarations[0].init?.type !== 'CallExpression') return;
|
||||
if (node.declarations[0].init.callee.type !== 'Identifier') return;
|
||||
if (node.declarations[0].init.callee.name !== '_export_sfc') return;
|
||||
if (node.declarations[0].init.arguments.length !== 2) return;
|
||||
if (node.declarations[0].init.arguments[0].type !== 'Identifier') return;
|
||||
const ident = node.declarations[0].init.arguments[0].name;
|
||||
if (!ident.startsWith('_sfc_main')) return;
|
||||
if (node.declarations[0].init.arguments[1].type !== 'ArrayExpression') return;
|
||||
if (node.declarations[0].init.arguments[1].elements.length !== 1) return;
|
||||
if (node.declarations[0].init.arguments[1].elements[0]?.type !== 'ArrayExpression') return;
|
||||
if (node.declarations[0].init.arguments[1].elements[0].elements.length !== 2) return;
|
||||
if (node.declarations[0].init.arguments[1].elements[0].elements[0]?.type !== 'Literal') return;
|
||||
if (node.declarations[0].init.arguments[1].elements[0].elements[0].value !== '__cssModules') return;
|
||||
if (node.declarations[0].init.arguments[1].elements[0].elements[1]?.type !== 'Identifier') return;
|
||||
const cssModuleForestName = node.declarations[0].init.arguments[1].elements[0].elements[1].name;
|
||||
const cssModuleForestNode = parent.body.find((x) => {
|
||||
if (x.type !== 'VariableDeclaration') return false;
|
||||
if (x.declarations.length !== 1) return false;
|
||||
|
@ -68,7 +44,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
|
|||
if (x.type !== 'VariableDeclaration') return false;
|
||||
if (x.declarations.length !== 1) return false;
|
||||
if (x.declarations[0].id.type !== 'Identifier') return false;
|
||||
if (x.declarations[0].id.name !== '_sfc_main') return false;
|
||||
if (x.declarations[0].id.name !== ident) return false;
|
||||
return true;
|
||||
}) as unknown as estree.VariableDeclaration;
|
||||
if (sfcMain.declarations[0].init?.type !== 'CallExpression') return;
|
||||
|
@ -88,8 +64,10 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
|
|||
return true;
|
||||
}) as unknown as estree.ReturnStatement;
|
||||
if (render.argument?.type !== 'ArrowFunctionExpression') return;
|
||||
if (render.argument.params.length !== 2) return;
|
||||
const ctx = render.argument.params[0];
|
||||
if (ctx.type !== 'Identifier') return;
|
||||
if (ctx.name !== '_ctx') return;
|
||||
if (render.argument.body.type !== 'BlockStatement') return;
|
||||
//console.dir(render, { depth: Infinity });
|
||||
for (const [key, value] of moduleForest) {
|
||||
|
@ -133,6 +111,21 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
|
|||
});
|
||||
},
|
||||
});
|
||||
this.replace({
|
||||
type: 'VariableDeclaration',
|
||||
declarations: [{
|
||||
type: 'VariableDeclarator',
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
name: node.declarations[0].id.name,
|
||||
},
|
||||
init: {
|
||||
type: 'Identifier',
|
||||
name: ident,
|
||||
},
|
||||
}],
|
||||
kind: 'const',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -143,12 +136,8 @@ export default function pluginUnwindCssModuleClassName(): Plugin {
|
|||
return {
|
||||
name: 'UnwindCssModuleClassName',
|
||||
renderChunk(code, chunk): { code: string } {
|
||||
console.log(`=======${chunk.fileName} BEFORE=======`);
|
||||
console.log(code);
|
||||
const ast = this.parse(code) as unknown as estree.Node;
|
||||
unwindCssModuleClassName(ast);
|
||||
console.log(`=======${chunk.fileName} AFTER=======`);
|
||||
console.log(generate(ast));
|
||||
return { code: generate(ast) };
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue