Skip to content

Commit 93d1428

Browse files
refactor-botWscats
authored andcommitted
refactor: complete JS to TypeScript migration for all libs/
- Convert all 20 JS source files in libs/ to TypeScript - Create libs/types.ts with 12 interfaces (SourceObject, CompileContext, etc.) - Replace all require() with ES module imports - Replace all module.exports with export/export default - Add type annotations to all functions and parameters - Type Babel AST visitor nodes - Add OmilApiResult, ScriptSourceObject, DefineOption, RenderOption interfaces - Use dynamic import() for lazy-loaded modules - Use 'export =' for webpack loader CommonJS compatibility - Keep sass.sync.js as-is (compiled binary) - Update tsconfig: allowJs=false, rootDir=libs, include libs+src
1 parent 5ee4f6c commit 93d1428

30 files changed

Lines changed: 510 additions & 487 deletions

libs/index.js renamed to libs/index.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,25 @@
33
* Webpack loader and programmatic API for compiling Omi single-file components.
44
*/
55

6-
'use strict';
6+
import compileAll from './loaders/index';
7+
import { compileSass } from './styles/extension/index';
8+
import type { SourceObject, CompilationResult } from './types';
79

8-
const compileAll = require('./loaders/index');
10+
/** Return type for programmatic API. */
11+
interface OmilApiResult {
12+
compileSass: typeof compileSass;
13+
}
914

1015
/**
1116
* Omil loader / API entry.
1217
* When called with an object (programmatic API), returns compiled output.
1318
* When called as a webpack loader, uses async callback.
14-
*
15-
* @param {string|object} source - The source code or source object.
16-
* @returns {object|void} Compiled output when used programmatically.
1719
*/
18-
module.exports = function (source) {
20+
function omil(this: any, source: string | SourceObject): OmilApiResult | void {
1921
// Programmatic API (used by omi-snippets)
2022
if (typeof source === 'object') {
21-
const callback = (_info, code) => code;
22-
compileAll(source, source.options, callback);
23-
const { compileSass } = require('./styles/extension/index');
23+
const callback = (_info: CompilationResult, code: string): string => code;
24+
compileAll(source, source.options ?? null, callback as any);
2425
return { compileSass };
2526
}
2627

@@ -29,4 +30,6 @@ module.exports = function (source) {
2930
const { getOptions } = require('loader-utils');
3031
const options = getOptions(this) || {};
3132
compileAll({ source }, options, callback);
32-
};
33+
}
34+
35+
export = omil;
Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@
33
* Orchestrates template, style, and script compilation for Omi single-file components.
44
*/
55

6-
'use strict';
7-
8-
const compileStyle = require('../styles/index');
9-
const compileTemplate = require('../templates/index');
10-
const compileScript = require('../scripts/index');
11-
const modulesStart = require('../scripts/modules/import');
12-
const modulesEnd = require('../scripts/modules/export');
13-
const defineComponent = require('../scripts/modules/define');
14-
const renderComponent = require('../scripts/modules/render');
15-
const handleStyledComponents = require('../templates/styledcomponents');
16-
const ast = require('../scripts/ast');
17-
const { compileSass } = require('../styles/extension/index');
18-
const classProperty = require('../scripts/extension/classProperty');
6+
import compileStyle from '../styles/index';
7+
import compileTemplate from '../templates/index';
8+
import compileScript from '../scripts/index';
9+
import generateImports from '../scripts/modules/import';
10+
import generateExport from '../scripts/modules/export';
11+
import generateDefine from '../scripts/modules/define';
12+
import generateRender from '../scripts/modules/render';
13+
import handleStyledComponents from '../templates/styledcomponents/index';
14+
import astTransform from '../scripts/ast/index';
15+
import { compileSass } from '../styles/extension/index';
16+
import classProperty from '../scripts/extension/classProperty';
17+
import type { SourceObject, CompilationResult, CompileContext, AstOption, LoaderCallback } from '../types';
1918

2019
/** Regex to match export/module.exports class patterns. */
2120
const EXPORT_REGEX = 'export\\s+default\\s*\\{|module.exports\\s*=\\s*\\{|export\\s+default\\s*class\\s*\\{|module.exports\\s*=\\s*class\\s*\\{|export\\s+default[\\n\\s\\S]+?class[\\s\\w]*\\{|module.exports\\s*=[\\n\\s\\S]*?class\\s*\\{';
@@ -28,12 +27,12 @@ const EXPORT_REGEX = 'export\\s+default\\s*\\{|module.exports\\s*=\\s*\\{|export
2827
* 4. Generate import/export/define/render module code
2928
* 5. Run AST transformations
3029
* 6. Optionally transpile ES6 → ES5 for webpack loader mode
31-
*
32-
* @param {object} sourceObj - The source object containing the raw component.
33-
* @param {object} options - Compilation options (e.g., Babel config).
34-
* @param {Function} callback - Callback with (error, code, map) or status object.
3530
*/
36-
const compileAll = async (sourceObj, options, callback) => {
31+
async function compileAll(
32+
sourceObj: SourceObject,
33+
options: Record<string, unknown> | null,
34+
callback: ((result: CompilationResult) => void) | LoaderCallback,
35+
): Promise<void> {
3736
// 1. Extract template
3837
let { template, templateLang, templateComponentName, templateFrameworkName } =
3938
compileTemplate(sourceObj);
@@ -59,7 +58,7 @@ const compileAll = async (sourceObj, options, callback) => {
5958
styleLang,
6059
isExistStyle,
6160
templateComponentName,
62-
});
61+
} as any);
6362

6463
let script = rawScript;
6564

@@ -69,12 +68,12 @@ const compileAll = async (sourceObj, options, callback) => {
6968
// 5. Transform template (JSX/TSX via Babel)
7069
try {
7170
if (templateLang !== 'html' && templateLang !== 'htm') {
72-
const transformTemplate = require('../scripts/extension/transform');
71+
const transformTemplate = (await import('../scripts/extension/transform')).default;
7372
const result = await transformTemplate(template, { sourceType: 'script' });
7473
template = result.code;
7574
}
7675
} catch (e) {
77-
callback({ status: 'fail', allScript: '', e });
76+
(callback as (result: CompilationResult) => void)({ status: 'fail', allScript: '', e: e as Error });
7877
throw new Error('Babel compile failed, see issues https://github.com/Wscats/eno-loader/issues');
7978
}
8079

@@ -83,45 +82,46 @@ const compileAll = async (sourceObj, options, callback) => {
8382
const exportReplaceReg = new RegExp(EXPORT_REGEX, 'g');
8483
const exportCompileReg = new RegExp('((' + EXPORT_REGEX + ')[\\s\\S]+)', 'g');
8584

86-
const compileContext = {
85+
const compileContext: CompileContext = {
8786
script, isExistScript, scriptLang, template,
8887
templateLang, templateComponentName, templateFrameworkName,
8988
style, styleLang, isExistStyle, sourceObj,
9089
};
9190

9291
// Generate import header
93-
const importCode = modulesStart(compileContext);
92+
const importCode = generateImports(compileContext);
9493

9594
// Generate export/class body and handle class properties
96-
const exportCode = script.match(exportCompileReg)[0];
97-
script = script.replace(exportReplaceReg, modulesEnd(compileContext));
95+
const exportCode = script.match(exportCompileReg)![0];
96+
script = script.replace(exportReplaceReg, generateExport(compileContext));
9897
script = script.replace(exportCompileReg, classProperty(exportCode).code);
9998

10099
// Combine: imports + script body + define() + render()
101100
let allScript = importCode + script +
102-
defineComponent({ script, templateComponentName }) +
103-
renderComponent({ templateComponentName, sourceObj });
101+
generateDefine({ script, templateComponentName }) +
102+
generateRender({ templateComponentName, sourceObj });
104103

105104
// 7. AST transformation (JSX → h() calls, cleanup)
106-
allScript = (await ast({
105+
const astOption: AstOption = {
107106
script, allScript, isExistScript, scriptType,
108107
scriptLang, template, templateLang, templateComponentName,
109-
style, styleLang, isExistStyle,
110-
}, null)).code;
108+
templateFrameworkName, style, styleLang, isExistStyle, sourceObj,
109+
};
110+
allScript = (await astTransform(astOption, null)).code;
111111

112112
// 8. Output
113113
if (sourceObj.type === 'extension') {
114-
callback({ status: 'success', allScript, e: null });
114+
(callback as (result: CompilationResult) => void)({ status: 'success', allScript, e: null });
115115
} else {
116-
const transformLoader = require('../scripts/loader/transform');
117-
const result = await transformLoader(allScript, options);
118-
callback(null, result.code, result.map);
116+
const transformLoader = (await import('../scripts/loader/transform')).default;
117+
const result = await transformLoader(allScript, options as any);
118+
(callback as LoaderCallback)(null, result.code, result.map);
119119
}
120120
} catch (e) {
121-
callback({ status: 'fail', allScript: '', e });
121+
(callback as (result: CompilationResult) => void)({ status: 'fail', allScript: '', e: e as Error });
122122
console.error(e);
123123
throw new Error('Babel compile failed, see issues https://github.com/Wscats/eno-loader/issues');
124124
}
125-
};
125+
}
126126

127-
module.exports = compileAll;
127+
export default compileAll;

libs/scripts/ast/index.js

Lines changed: 0 additions & 105 deletions
This file was deleted.

libs/scripts/ast/index.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Omil - AST transformation module.
3+
* Uses Babel to transform compiled component code (JSX → h() calls, class properties, etc.).
4+
*/
5+
6+
import { transform } from '@babel/core';
7+
import type { AstOption, BabelOptions } from '../../types';
8+
9+
interface AstResult {
10+
code: string;
11+
}
12+
13+
/**
14+
* Remove duplicate render/css methods from WeElement classes.
15+
* Keeps only the first render method and removes all css methods.
16+
*/
17+
function cleanupWeElementBody(path: any): void {
18+
const renderMethods: any[] = [];
19+
path.get('body.body').forEach((classMethod: any) => {
20+
const name = classMethod.node.key.name;
21+
if (name === 'render') {
22+
renderMethods.push(classMethod);
23+
} else if (name === 'css') {
24+
classMethod.remove();
25+
}
26+
});
27+
28+
// Keep only the first render method, remove duplicates
29+
for (let i = 1; i < renderMethods.length; i++) {
30+
renderMethods[i].remove();
31+
}
32+
}
33+
34+
/**
35+
* Transform the compiled component code using Babel.
36+
* Handles JSX pragma, class properties, and WeElement class cleanup.
37+
*/
38+
export default function astTransform(
39+
option: AstOption,
40+
options: BabelOptions | null,
41+
): Promise<AstResult> {
42+
const { allScript, scriptType } = option;
43+
44+
return new Promise((resolve, reject) => {
45+
const presets: unknown[] = [
46+
[require('@babel/preset-react'), { pragma: 'h' }],
47+
];
48+
49+
if (scriptType === 'text/babel') {
50+
presets.push(require('@babel/preset-env'));
51+
}
52+
53+
/** Babel visitor to clean up WeElement classes. */
54+
const weElementClassVisitor = {
55+
ClassExpression(path: any): void {
56+
if (!path.node.superClass || path.node.superClass.name !== 'WeElement') {
57+
return;
58+
}
59+
cleanupWeElementBody(path);
60+
},
61+
62+
ImportDeclaration(path: any): void {
63+
if (scriptType === 'module') {
64+
path.remove();
65+
}
66+
},
67+
68+
ClassDeclaration(path: any): void {
69+
if (!path.node.superClass || path.node.superClass.name !== 'WeElement') {
70+
return;
71+
}
72+
cleanupWeElementBody(path);
73+
},
74+
};
75+
76+
const defaultOption = {
77+
plugins: [
78+
[require('@babel/plugin-proposal-class-properties'), { loose: true }],
79+
{ visitor: weElementClassVisitor },
80+
],
81+
comments: false,
82+
presets,
83+
};
84+
85+
const finalOptions = { ...defaultOption, ...options };
86+
87+
transform(allScript, finalOptions, (err, result) => {
88+
if (err) {
89+
reject(err);
90+
} else {
91+
resolve({ code: result?.code || '' });
92+
}
93+
});
94+
});
95+
}

0 commit comments

Comments
 (0)