เป้าหมายคือย้ายทั้งโปรเจคจาก "global script + window.* + inline handler" ไปเป็น "ES modules + clear boundaries + sensor plugin contract" โดยยังเปิดแอปได้ทุกช่วงของการย้าย
- มี
main.jsเป็น entry point เดียว - ทุกไฟล์ใน
src/ใช้import/export - ไม่มีการพึ่ง script order ใน
index.html - sensor ทุกตัวมี
index.jsและ export plugin มาตรฐาน window.*เหลือเฉพาะ bridge ชั่วคราวหรือ public API ที่ตั้งใจเปิดจริง- event binding ย้ายออกจาก HTML ไปอยู่ใน JS
- physics, storage, rendering, runtime แยกบทบาทชัด
ไฟล์หลัก: index.html
- เพิ่ม
src/main.jsเป็นตัว import ทุก subsystem - ลด
<script>หลายตัวใน HTML ให้เหลือvendorที่จำเป็นกับmain.js - คง global bridge เฉพาะที่ยังต้องใช้กับ inline HTML และ legacy files
- ตั้งกติกาใหม่ว่าไฟล์ใหม่ทุกไฟล์ต้องเป็น ESM เท่านั้น
ผลลัพธ์ที่ต้องได้:
- แอปยังเปิดได้
- ไม่มีการพึ่ง "โหลดไฟล์นี้ก่อน/หลัง" แบบ implicit ในส่วน core ใหม่
ไฟล์หลัก:
src/core/variableGlobal.jssrc/core/sensor-plugin-registry.jssrc/core/simulation-service.jssrc/core/physics/physics-adapter.js
งานที่ต้องทำ:
- แยก
state, DOM refs, constants, registry, simulation service, physics adapter เป็น module ที่ import ได้ - ห้าม module ใหม่อ่าน
window.stateตรง ถ้าไม่จำเป็น - ทำ
dom-refs.jsแยกจากstateเพื่อไม่ให้ logic ต้อง import DOM ปนไปด้วย - ทำ
services/index.jsหรือcore/index.jsสำหรับ export รวม
ผลลัพธ์ที่ต้องได้:
- core modules import กันตรง ๆ ได้
- bridge ไป
windowเป็น optional compatibility layer เท่านั้น
โครงเป้าหมายต่อ sensor:
config.jsonrender.htmlindex.jslogic.jsphysics.jsexecutor.js
contract ที่ควรบังคับ:
create(id, index)drawPreview(svg, sensor)drawCanvas(svg, sensor, globals, index)read(sensor, globals)updateValue(idOrKey, axis, value)physicsStep(sensor, index, globals)registerPythonAPI(Sk, robotObj, globals)
งานที่ต้องทำ:
- เขียนเอกสาร contract กลาง
- ทำ helper validator ว่า plugin ไหนขาด method อะไร
- ให้ loader register plugin ผ่าน
registerSensorPlugin(...)อย่างเดียว
ผลลัพธ์ที่ต้องได้:
- sensor ใหม่เขียนตามรูปแบบเดียวกัน
- ลด logic พิเศษเฉพาะตัวใน manager
ตอนนี้เริ่มแล้วกับ light
ลำดับที่แนะนำ:
lightultrasoniccompassgripwheelrobotobject
ต่อ sensor แต่ละตัว:
- เพิ่ม
index.js - เปลี่ยน
logic.js,physics.js,executor.jsให้ export function/object - ตัดการ assign ตรง ๆ แบบ
window.SensorRegistry["x"] = ... - เปลี่ยนไป export plugin แล้ว register ผ่าน loader หรือใน
index.js
ผลลัพธ์ที่ต้องได้:
- ทุก sensor โหลดผ่าน module path ได้
- legacy fallback ถูกลบได้ในท้าย phase นี้
ไฟล์หลัก:
src/utils/sensorLoader.jsconfig.json
งานที่ต้องทำ:
- เปลี่ยน loader ให้ใช้
import()อย่างเดียว - อ่านรายชื่อ sensor จาก
config.json - โหลด
config.json,render.html,index.js - ย้าย
window.SensorConfigs,window.SensorTemplatesไปเป็น exported stores หรือ registry state ภายใน
ข้อจำกัดสำคัญ:
- ถ้าไม่มี build step/backend ยังต้องระบุ sensor names ใน
config.json - browser ไม่สามารถ scan folders เองได้
ผลลัพธ์ที่ต้องได้:
- ไม่มี dynamic
<script>injection แบบ legacy แล้ว
ไฟล์หลัก:
src/core/physics.jssrc/core/physics/custom-engine.jssrc/core/physics/matter-engine.jssrc/core/physics/differential-drive.js
งานที่ต้องทำ:
- ให้
physics.jsเป็น loop orchestration อย่างเดียว - ให้
custom-engineและmatter-engineเป็น adapter implementations - ย้าย shared sensor-physics dispatch ออกมาที่
SimulationService.runSensorPhysics() - ลดการที่ engine เรียก UI functions หรือ console โดยตรง
- ทำ
PhysicsPortภายในทีมแม้เป็น JS ปกติก็ได้
ผลลัพธ์ที่ต้องได้:
- เปลี่ยน engine ได้โดยกระทบ UI น้อย
- sensor ไม่ต้องรู้ว่ากำลังใช้ Matter หรือ custom
ไฟล์หลัก: src/core/executor.js
งานที่ต้องทำ:
- แยกส่วน Skulpt runtime bootstrap ออกจาก robot API registration
- แยก
preprocessCode,runCode,stopProgram, button state logic ออกจากกัน - ให้ sensor python APIs register ผ่าน imported plugin registry แทน lookup global
- ทำ runtime service เช่น
python-runtime.js
ผลลัพธ์ที่ต้องได้:
- Python API เพิ่มตาม sensor plugin ได้ง่าย
- executor ไม่ต้องรู้รายละเอียด sensor รายตัว
ไฟล์หลัก: index.html
งานที่ต้องทำ:
- ลบ
onclick,onchange,href="javascript:void(0)" - ใส่
id/data-actionให้ element - bind event ใน module เช่น
toolbar-controller.js,canvas-controller.js,file-menu-controller.js - ให้ HTML เหลือโครง markup อย่างเดียว
ผลลัพธ์ที่ต้องได้:
- ไม่มี global function แค่เพื่อให้ HTML เรียก
- test UI interactions ได้ง่ายขึ้นมาก
ไฟล์หลัก:
src/script.jssrc/core/canvas.jssrc/core/physics/sensor-physics.jssrc/sensors/sensors-manager.js
แนะนำแยกเป็น:
renderers/robot-renderer.jsrenderers/sensor-preview-renderer.jsrenderers/canvas-overlay-renderer.jscontrollers/sensor-settings-controller.js
ผลลัพธ์ที่ต้องได้:
- mutation ของ state ไม่ปนกับ DOM rendering
- render functions เรียกซ้ำได้และ debug ง่าย
เมื่อ phase ก่อน ๆ เสร็จแล้ว ค่อยลบ:
window.statewindow.SensorRegistrywindow.switchTabwindow.logToConsole- global helpers ที่เหลือ
- fallback loader ที่ยัง inject legacy scripts
ผลลัพธ์ที่ต้องได้:
- ระบบเป็น module-native จริง
- dependency graph ชัดและไม่มี dual-mode ซ้อน
แม้ไม่มี Node modules ก็ยังทำ smoke tests ได้แบบ manual checklist หรือ browser-based test page
อย่างน้อยควรมี checklist:
- app boot
- load project / save project
- run / stop python
- switch custom / matter physics
- add/remove sensor ทุกชนิด
- sensor preview
- light/ultrasonic/compass/grip APIs
- object interaction
- language switch
ควรมี test fixtures:
- example projects เดิม
- 1 project ต่อ 1 sensor type
- 1 mixed sensor project
ถ้าจะลงมือทีละรอบ แนะนำลำดับนี้:
main.js+ module bootstrap- core shared modules
sensorLoadermodule-first- ย้าย sensor ทั้งหมด
executor.jssensors-manager.jsscript.jsและ renderers- ลบ inline handlers จาก HTML
- ลบ legacy bridge
index.htmlยังมี inline handlers เยอะ ถ้าย้ายไม่ครบจะมี function not found- sensor หลายตัวพึ่ง helper globals ข้ามไฟล์
- Skulpt integration อ่อนไหวกับ timing และ globals
- Matter/custom physics มี logic ซ้ำบางส่วน อาจเกิด regression ตอนรวม flow
ถือว่า migration เสร็จเมื่อ:
src/ใช้import/exportทั้งหมด- ไม่มี legacy
<script src="...">สำหรับไฟล์แอป ยกเว้น vendor - ไม่มี
window.SensorRegistry["x"] = ... - ไม่มี inline handler ใน HTML
- sensor ใหม่เพิ่มโดยทำ
folder + config.json + render.html + index.js - app ยังเปิดและฟีเจอร์หลักทั้งหมดทำงาน