Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions packages/devextreme/build/vite-plugin-devextreme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { transformAsync } from '@babel/core';
import type { PluginOption } from 'vite';

function removeUninitializedClassFields(): unknown {
return {
visitor: {
ClassProperty(path: { node: { value: unknown }; remove: () => void }) {
if (path.node.value === null || path.node.value === undefined) {
path.remove();
}
},
},
};
}

function moveFieldInitializersToConstructor(): unknown {
return {
visitor: {
Class(path: { node: { body: { body: unknown[] } } }) {
const body = path.node.body.body;

type ClassMember = {
type: string;
kind?: string;
key?: { name: string };
value?: unknown;
static?: boolean;
body?: { body: unknown[] };
};

const fieldsToMove: ClassMember[] = [];
const remaining: unknown[] = [];

for (const member of body as ClassMember[]) {
if (
member.type === 'ClassProperty'
&& member.value != null
&& !member.static
) {
fieldsToMove.push(member);
} else {
remaining.push(member);
}
}

if (fieldsToMove.length === 0) return;

const ctor = (remaining as ClassMember[]).find(
(m) => m.type === 'ClassMethod' && m.kind === 'constructor',
);

if (!ctor) return;

const assignments = fieldsToMove.map((field) => ({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
object: { type: 'ThisExpression' },
property: { type: 'Identifier', name: field.key!.name },
computed: false,
},
right: field.value,
},
}));

type Stmt = {
type: string;
expression?: {
type: string;
operator?: string;
callee?: { type: string };
left?: { type: string; object?: { type: string }; property?: { name: string } };
right?: { type: string; name?: string };
};
};

type Param = { type: string; name?: string; left?: { name?: string } };
const paramNames = new Set<string>();
for (const param of ((ctor as unknown as { params: Param[] }).params ?? [])) {
if (param.type === 'Identifier' && param.name) {
paramNames.add(param.name);
} else if (param.type === 'AssignmentPattern' && param.left?.name) {
paramNames.add(param.left.name);
}
}

const ctorBody = ctor.body!.body as Stmt[];

let insertAt = 0;
while (insertAt < ctorBody.length) {
const stmt = ctorBody[insertAt];
const isSuperCall = stmt.type === 'ExpressionStatement'
&& stmt.expression?.type === 'CallExpression'
&& stmt.expression.callee?.type === 'Super';
const isParamPropertyAssignment = stmt.type === 'ExpressionStatement'
&& stmt.expression?.type === 'AssignmentExpression'
&& stmt.expression.operator === '='
&& stmt.expression.left?.type === 'MemberExpression'
&& stmt.expression.left.object?.type === 'ThisExpression'
&& stmt.expression.right?.type === 'Identifier'
&& stmt.expression.left.property?.name === stmt.expression.right.name
&& paramNames.has(stmt.expression.right.name!);

if (!isSuperCall && !isParamPropertyAssignment) break;
insertAt += 1;
}

ctorBody.splice(insertAt, 0, ...(assignments as Stmt[]));

path.node.body.body = remaining;
},
},
};
}

export default function devextremeVitePlugin(): PluginOption {
return {
name: 'vite-plugin-devextreme',
enforce: 'pre',

async transform(code: string, id: string) {
if (!/\.[jt]sx?$/.test(id) || id.includes('node_modules')) {
return null;
}

const isTSX = id.endsWith('.tsx');
const isTS = id.endsWith('.ts') || isTSX;

const plugins: unknown[] = [];

if (isTS) {
plugins.push([
'@babel/plugin-transform-typescript',
{
isTSX,
allExtensions: true,
allowDeclareFields: true,
optimizeConstEnums: true,
},
]);
}

plugins.push(
removeUninitializedClassFields,
moveFieldInitializersToConstructor,
['@babel/plugin-proposal-decorators', { legacy: true }],
'babel-plugin-inferno',
);

const result = await transformAsync(code, {
filename: id,
plugins,
sourceMaps: true,
});

if (!result?.code) {
return null;
}

return { code: result.code, map: result.map };
},
};
}
1 change: 1 addition & 0 deletions packages/devextreme/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default [
'themebuilder-scss/src/data/metadata/*',
'js/bundles/dx.custom.js',
'testing/jest/utils/transformers/*',
'vite.config.ts',
'**/ts/',
'js/common/core/localization/cldr-data/*',
'js/common/core/localization/default_messages.js',
Expand Down
4 changes: 4 additions & 0 deletions packages/devextreme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@
"@babel/core": "7.29.0",
"@babel/eslint-parser": "catalog:",
"@babel/parser": "7.29.2",
"@babel/plugin-proposal-decorators": "7.25.9",
"@babel/plugin-transform-modules-commonjs": "7.28.6",
"@babel/plugin-transform-typescript": "7.28.5",
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.2",
"@devextreme-generator/angular": "3.0.12",
Expand Down Expand Up @@ -211,6 +213,7 @@
"typescript-min": "npm:typescript@4.9.5",
"uuid": "9.0.1",
"vinyl": "2.2.1",
"vite": "^7.3.2",
"vinyl-named": "1.1.0",
"webpack": "5.105.4",
"webpack-stream": "7.0.0",
Expand Down Expand Up @@ -247,6 +250,7 @@
"validate-ts": "gulp validate-ts",
"validate-declarations": "dx-tools validate-declarations --sources ./js --exclude \"js/(renovation|__internal|.eslintrc.js)\" --compiler-options \"{ \\\"typeRoots\\\": [] }\"",
"testcafe-in-docker": "docker build -f ./testing/testcafe/docker/Dockerfile -t testcafe-testing . && docker run -it testcafe-testing",
"dev:playground": "vite",
"test-jest": "cross-env NODE_OPTIONS='--expose-gc' jest --no-coverage --runInBand --selectProjects jsdom-tests",
"test-jest:watch": "jest --watch",
"test-jest:node": "jest --no-coverage --runInBand --selectProjects node-tests",
Expand Down
16 changes: 16 additions & 0 deletions packages/devextreme/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevExtreme Playground</title>
<link rel="stylesheet" href="./themeSelector.css">
</head>
<body class="dx-viewport">
<div id="app">
<div id="theme-selector"></div>
<div id="container"></div>
</div>
<script type="module" src="./index.ts"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions packages/devextreme/playground/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import '../js/__internal/integration/jquery';
import '../js/ui/card_view';
import $ from 'jquery';
import { setupThemeSelector } from './themeSelector.ts';

const customers = [
{ ID: 1, Company: 'Super Mart of the West', Address: '702 SW 8th Street', City: 'Bentonville', State: 'Arkansas', Zipcode: 72716, Phone: '(800) 555-2797' },
{ ID: 2, Company: 'Electronics Depot', Address: '2455 Paces Ferry Road NW', City: 'Atlanta', State: 'Georgia', Zipcode: 30339, Phone: '(800) 595-3232' },
{ ID: 3, Company: 'K&S Music', Address: '1000 Nicllet Mall', City: 'Minneapolis', State: 'Minnesota', Zipcode: 55403, Phone: '(612) 304-6073' },
{ ID: 4, Company: "Tom's Club", Address: '999 Lake Drive', City: 'Issaquah', State: 'Washington', Zipcode: 98027, Phone: '(800) 955-2292' },
{ ID: 5, Company: 'E-Mart', Address: '3333 Beverly Rd', City: 'Hoffman Estates', State: 'Illinois', Zipcode: 60179, Phone: '(847) 286-2500' },
{ ID: 6, Company: 'Walters', Address: '200 Wilmot Rd', City: 'Deerfield', State: 'Illinois', Zipcode: 60015, Phone: '(847) 940-2500' },
{ ID: 7, Company: 'StereoShack', Address: '400 Commerce S', City: 'Fort Worth', State: 'Texas', Zipcode: 76102, Phone: '(817) 820-0741' },
{ ID: 8, Company: 'Circuit Town', Address: '2200 Kensington Court', City: 'Oak Brook', State: 'Illinois', Zipcode: 60523, Phone: '(800) 955-2929' },
{ ID: 9, Company: 'Premier Buy', Address: '7601 Penn Avenue South', City: 'Richfield', State: 'Minnesota', Zipcode: 55423, Phone: '(612) 291-1000' },
{ ID: 10, Company: 'ElectrixMax', Address: '263 Shuman Blvd', City: 'Naperville', State: 'Illinois', Zipcode: 60563, Phone: '(630) 438-7800' },
{ ID: 11, Company: 'Video Emporium', Address: '1201 Elm Street', City: 'Dallas', State: 'Texas', Zipcode: 75270, Phone: '(214) 854-3000' },
{ ID: 12, Company: 'Screen Shop', Address: '1000 Lowes Blvd', City: 'Mooresville', State: 'North Carolina', Zipcode: 28117, Phone: '(800) 445-6937' },
];

window.addEventListener('load', () =>
setupThemeSelector('theme-selector').then(() => {
$('#container').dxCardView({
dataSource: customers,
keyExpr: 'ID',
cardsPerRow: 'auto',
cardMinWidth: 320,
columns: ['Company', 'Address', 'City', 'State', 'Zipcode', 'Phone'],
});
}));
Loading
Loading