mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(lint): recognize Three.js loaded via ESM URL/path imports (#1805)
The missing_three_script rule only treated a bare import from 'three' as loading Three.js, so ESM imports whose specifier is a URL or path (e.g. .../+esm CDN builds, esm.sh/three, unpkg three.module.js, or a local three.module.js) were not recognized. Compositions using THREE. with such an import got a false blocking error, pushing authors onto the deprecated UMD global build. Generalize the module-import detection to count any import/from whose specifier contains "three" (case-insensitive), matching the existing loose /three/i treatment of <script src>. Bare 'three', importmap, and <script src> paths are unchanged; the specifier must still contain "three", so unrelated imports do not satisfy it.
This commit is contained in:
@@ -124,6 +124,88 @@ describe("adapter rules", () => {
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not report missing_three_script for an ESM +esm CDN import (jsdelivr)", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080"></div>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160/+esm';
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
const scene = new THREE.Scene();
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "missing_three_script");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not report missing_three_script for an esm.sh/three import", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080"></div>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://esm.sh/three';
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
const scene = new THREE.Scene();
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "missing_three_script");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not report missing_three_script for a local three.module.js import", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080"></div>
|
||||
<script type="module">
|
||||
import { Scene } from './vendor/three.module.js';
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
const scene = new Scene();
|
||||
THREE.foo();
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "missing_three_script");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not report missing_three_script for a bare 'three' import (regression)", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080"></div>
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
const scene = new THREE.Scene();
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "missing_three_script");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("still reports missing_three_script when THREE is used with no three loaded", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080"></div>
|
||||
<script type="module">
|
||||
import { gsap } from 'https://cdn.jsdelivr.net/npm/gsap@3/+esm';
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
const scene = new THREE.Scene();
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "missing_three_script");
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.severity).toBe("error");
|
||||
});
|
||||
|
||||
it("does not report any adapter errors for composition with no adapter usage", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
|
||||
@@ -36,8 +36,11 @@ export const adapterRules: Array<(ctx: LintContext) => HyperframeLintFinding[]>
|
||||
/["']three["']/.test(t) &&
|
||||
/importmap/.test(scripts.find((s) => s.content === t)?.attrs || ""),
|
||||
);
|
||||
const hasThreeModuleImport = texts.some(
|
||||
(t) => /\bimport\b.*['"]three['"]/.test(t) || /\bfrom\s+['"]three['"]/.test(t),
|
||||
// Matches any import/from whose specifier contains "three" (bare 'three', or a
|
||||
// URL/path like .../+esm, esm.sh/three, three.module.js), mirroring the loose
|
||||
// /three/i treatment of <script src>.
|
||||
const hasThreeModuleImport = texts.some((t) =>
|
||||
/\b(?:import|from)\s*[^;\n]*['"][^'"]*three[^'"]*['"]/i.test(t),
|
||||
);
|
||||
|
||||
if (!usesThree || hasThreeScript || hasThreeImportMap || hasThreeModuleImport) return [];
|
||||
|
||||
Reference in New Issue
Block a user