Bundle CreateJS, instead of loading async
So I finally started looking into the race condition that makes item previews sometimes fail to load, and as expected, it was that we were trying to load the movie before CreateJS had necessarily loaded. Usually the timing worked out, esp after a reload, but not under certain circumstances! Anyway, I've been wanting for a while to just bundle them instead. That'll help us more eagerly load them when we need them, and not depend on external CDNs, and remove a bunch of loading state! So yeah, I had to learn how the `easeljs` and `tweenjs` NPM packages did their bundling, and how to use `imports-loader` to let them just register straight onto `window`! But we got there and it's pretty nice tbh!
This commit is contained in:
parent
bb5ec56752
commit
75ceeba6e2
4 changed files with 41 additions and 65 deletions
|
@ -29,6 +29,7 @@
|
|||
"canvas": "^2.6.1",
|
||||
"dataloader": "^2.0.0",
|
||||
"dompurify": "^2.2.0",
|
||||
"easeljs": "^1.0.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"framer-motion": "^4.1.11",
|
||||
"graphql": "^15.5.0",
|
||||
|
@ -49,6 +50,7 @@
|
|||
"react-scripts": "^4.0.1",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"simple-markdown": "^0.7.2",
|
||||
"tweenjs": "^1.0.2",
|
||||
"typescript": "^4.1.3",
|
||||
"xmlrpc": "^1.3.2"
|
||||
},
|
||||
|
@ -91,6 +93,7 @@
|
|||
}
|
||||
],
|
||||
"import/first": "off",
|
||||
"import/no-webpack-loader-syntax": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
|
@ -128,6 +131,7 @@
|
|||
"es6-promise-pool": "^2.5.0",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"husky": "^6.0.0",
|
||||
"imports-loader": "^3.0.0",
|
||||
"inquirer": "^7.3.3",
|
||||
"jest-image-snapshot": "^4.3.0",
|
||||
"lint-staged": "^10.5.4",
|
||||
|
|
|
@ -4,6 +4,12 @@ import { useToast } from "@chakra-ui/react";
|
|||
|
||||
import { loadImage, logAndCapture, safeImageUrl } from "../util";
|
||||
|
||||
// Import EaselJS and TweenJS directly into the `window` object! The bundled
|
||||
// scripts are built to attach themselves to `window.createjs`, and
|
||||
// `window.createjs` is where the Neopets movie libraries expects to find them!
|
||||
require("imports-loader?wrapper=window!easeljs/lib/easeljs");
|
||||
require("imports-loader?wrapper=window!tweenjs/lib/tweenjs");
|
||||
|
||||
function OutfitMovieLayer({
|
||||
libraryUrl,
|
||||
width,
|
||||
|
@ -19,8 +25,6 @@ function OutfitMovieLayer({
|
|||
const hasShownErrorMessageRef = React.useRef(false);
|
||||
const toast = useToast();
|
||||
|
||||
const loadingDeps = useEaselDependenciesLoader();
|
||||
|
||||
// Set the canvas's internal dimensions to be higher, if the device has high
|
||||
// DPI like retina. But we'll keep the layout width/height as expected!
|
||||
const internalWidth = width * window.devicePixelRatio;
|
||||
|
@ -63,7 +67,7 @@ function OutfitMovieLayer({
|
|||
React.useLayoutEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (loadingDeps || !canvas) {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -101,15 +105,11 @@ function OutfitMovieLayer({
|
|||
canvas.height = 0;
|
||||
}
|
||||
};
|
||||
}, [loadingDeps, libraryUrl, toast]);
|
||||
}, [libraryUrl, toast]);
|
||||
|
||||
// This effect gives us the `library` and `movieClip`, based on the incoming
|
||||
// `libraryUrl`.
|
||||
React.useEffect(() => {
|
||||
if (loadingDeps) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canceled = false;
|
||||
|
||||
loadMovieLibrary(libraryUrl)
|
||||
|
@ -132,7 +132,7 @@ function OutfitMovieLayer({
|
|||
setLibrary(null);
|
||||
setMovieClip(null);
|
||||
};
|
||||
}, [loadingDeps, libraryUrl]);
|
||||
}, [libraryUrl]);
|
||||
|
||||
// This effect puts the `movieClip` on the `stage`, when both are ready.
|
||||
React.useEffect(() => {
|
||||
|
@ -230,55 +230,6 @@ function OutfitMovieLayer({
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* useEaselDependenciesLoader loads the CreateJS scripts we use in OutfitCanvas.
|
||||
* We load it as part of OutfitCanvas, but callers can also use this to preload
|
||||
* the scripts and track loading progress.
|
||||
*/
|
||||
export function useEaselDependenciesLoader() {
|
||||
// NOTE: I couldn't find an official NPM source for this that worked with
|
||||
// Webpack, and I didn't want to rely on random people's ports, and I
|
||||
// couldn't get a bundled version to work quite right. So we load
|
||||
// createjs async!
|
||||
const loadingEaselJS = useScriptTag(
|
||||
"https://code.createjs.com/1.0.0/easeljs.min.js"
|
||||
);
|
||||
const loadingTweenJS = useScriptTag(
|
||||
"https://code.createjs.com/1.0.0/tweenjs.min.js"
|
||||
);
|
||||
const loadingDeps = loadingEaselJS || loadingTweenJS;
|
||||
|
||||
return loadingDeps;
|
||||
}
|
||||
|
||||
function useScriptTag(src) {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
const existingScript = document.querySelector(
|
||||
`script[src=${CSS.escape(src)}]`
|
||||
);
|
||||
if (existingScript) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let canceled = false;
|
||||
loadScriptTag(src).then(() => {
|
||||
if (!canceled) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
setLoading(true);
|
||||
};
|
||||
}, [src, setLoading]);
|
||||
|
||||
return loading;
|
||||
}
|
||||
|
||||
function loadScriptTag(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
|
|
|
@ -16,7 +16,6 @@ import OutfitMovieLayer, {
|
|||
buildMovieClip,
|
||||
hasAnimations,
|
||||
loadMovieLibrary,
|
||||
useEaselDependenciesLoader,
|
||||
} from "./OutfitMovieLayer";
|
||||
import HangerSpinner from "./HangerSpinner";
|
||||
import { loadImage, safeImageUrl, useLocalStorage } from "../util";
|
||||
|
@ -163,13 +162,10 @@ export function OutfitLayers({
|
|||
false
|
||||
);
|
||||
|
||||
const { loading: loadingEasel } = useEaselDependenciesLoader();
|
||||
const loadingAnything = loading || loadingEasel;
|
||||
|
||||
// When we start in a loading state, or re-enter a loading state, start the
|
||||
// loading delay timer.
|
||||
React.useEffect(() => {
|
||||
if (loadingAnything) {
|
||||
if (loading) {
|
||||
setLoadingDelayHasPassed(false);
|
||||
const t = setTimeout(
|
||||
() => setLoadingDelayHasPassed(true),
|
||||
|
@ -177,7 +173,7 @@ export function OutfitLayers({
|
|||
);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
}, [loadingDelayMs, loadingAnything]);
|
||||
}, [loadingDelayMs, loading]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
function computeAndSaveCanvasSize() {
|
||||
|
@ -291,7 +287,7 @@ export function OutfitLayers({
|
|||
// also use a timeout to delay the fade-in by 0.5s, but don't delay the
|
||||
// fade-out at all. (The timeout was an awkward choice, it was hard to
|
||||
// find a good CSS way to specify this delay well!)
|
||||
opacity={loadingAnything && loadingDelayHasPassed ? 1 : 0}
|
||||
opacity={loading && loadingDelayHasPassed ? 1 : 0}
|
||||
transition="opacity 0.2s"
|
||||
>
|
||||
{spinnerVariant === "overlay" && (
|
||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -9226,6 +9226,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
|||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
easeljs@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/easeljs/-/easeljs-1.0.2.tgz#68fdcc69d0f217394e2ebf51ae047428a81240de"
|
||||
integrity sha512-PQTsiud32vrUIqZCbynjOJjCzoEp0xH+MRusRCdsZ1MzL4LCE2vp4Sa5cr6aShB3mK4vMZ8LFPnts4xuFhjEmg==
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
|
@ -11426,6 +11431,16 @@ import-local@^3.0.2:
|
|||
pkg-dir "^4.2.0"
|
||||
resolve-cwd "^3.0.0"
|
||||
|
||||
imports-loader@1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
|
||||
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.6.1"
|
||||
strip-comments "^2.0.1"
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
|
@ -17718,6 +17733,11 @@ strip-comments@^1.0.2:
|
|||
babel-extract-comments "^1.0.0"
|
||||
babel-plugin-transform-object-rest-spread "^6.26.0"
|
||||
|
||||
strip-comments@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b"
|
||||
integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==
|
||||
|
||||
strip-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
|
@ -18348,6 +18368,11 @@ tunnel-agent@^0.6.0:
|
|||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tweenjs@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tweenjs/-/tweenjs-1.0.2.tgz#768869c4d4ba91fdb4c5ccc661969c58138555af"
|
||||
integrity sha512-WnFozCNkUkmJtLqJyGrToxVojW2Srzudktr8BzFKQijQRVcmlq7Fc+qfo75ccnwIJGiRWbXKfg7qU67Tzbb1bg==
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
|
Loading…
Reference in a new issue