Skip to content

Commit

Permalink
fix: [#483] Add optional JSDOM dependency for Node
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Felsinger committed Jan 16, 2024
1 parent 0517d2d commit c1273d0
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 77 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@playwright/test": "1.40.1",
"@types/jasmine": "5.1.4",
"@types/json-diff": "1.0.3",
"@types/jsdom": "21.1.6",
"@types/node": "20.10.6",
"@types/pako": "1.0.7",
"@types/webpack-env": "1.18.4",
Expand Down Expand Up @@ -77,6 +78,9 @@
"peerDependencies": {
"excalibur": "~0.28.5"
},
"optionalDependencies": {
"jsdom": "^23.2.0"
},
"overrides": {
"webpack-dev-server": {
"webpack-dev-middleware": "7.0.0"
Expand Down
38 changes: 31 additions & 7 deletions src/parser/tiled-parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { z } from 'zod';
import * as jsdom from 'jsdom';

const TiledIntProperty = z.object({
name: z.string(),
type: z.literal('int'),
Expand Down Expand Up @@ -497,6 +499,32 @@ export class TiledParser {
}
}

/**
* Takes an xml string and uses an available parser (DOMParser in browser or JSDOM in Node.js)
* to produce a DOM object compatible with at least DOM Level 3.
* @param xml
* @returns
*/
_parseToDocument(xml: string): Document {
if (typeof DOMParser !== 'undefined') {
const domParser = new DOMParser();
return domParser.parseFromString(xml, 'application/xml');
}

try {
const { JSDOM } = require('jsdom');
const dom = new JSDOM(xml, {
contentType: 'application/xml',
encoding: 'utf-8',
});
return dom.window.document as Document;
} catch (e) { /* ignored */ }

const error = new Error('Could not find DOM parser');
console.error(error.message, error);
throw error;
}

parseObject(objectNode: Element, strict = true): TiledObject {
const object: any = {};
object.type = '';
Expand Down Expand Up @@ -874,8 +902,7 @@ export class TiledParser {
}

parseExternalTemplate(txXml: string, strict = true): TiledTemplate {
const domParser = new DOMParser();
const doc = domParser.parseFromString(txXml, 'application/xml');
const doc = this._parseToDocument(txXml);
const templateElement = doc.querySelector('template') as Element;
const template: any = {};
template.type = 'template';
Expand Down Expand Up @@ -905,8 +932,7 @@ export class TiledParser {
* @param tsxXml
*/
parseExternalTileset(tsxXml: string, strict = true): TiledTilesetFile {
const domParser = new DOMParser();
const doc = domParser.parseFromString(tsxXml, 'application/xml');
const doc = this._parseToDocument(tsxXml);
const tilesetElement = doc.querySelector('tileset') as Element;

const tileset = this.parseTileset(tilesetElement, strict);
Expand All @@ -933,9 +959,7 @@ export class TiledParser {
* @returns
*/
parse(tmxXml: string, strict = true): TiledMap {
const domParser = new DOMParser();
const doc = domParser.parseFromString(tmxXml, 'application/xml');

const doc = this._parseToDocument(tmxXml);
const mapElement = doc.querySelector('map') as Element;

const tiledMap: any = {};
Expand Down
37 changes: 20 additions & 17 deletions src/resource/object-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class ObjectLayer implements Layer {
}

_actorFromObject(object: PluginObject, newActor: Actor, tileset?: Tileset): void {
const headless = this.resource.headless;
const hasTint = !!this.tiledObjectLayer.tintcolor;
const tint = this.tiledObjectLayer.tintcolor ? Color.fromHex(this.tiledObjectLayer.tintcolor) : Color.White;

Expand All @@ -109,26 +110,28 @@ export class ObjectLayer implements Layer {
const scaleY = (object.tiledObject.width ?? this.resource.map.tilewidth) / this.resource.map.tilewidth;
const scale = vec(scaleX, scaleY);

// need to clone because we are modify sprite properties, sprites are shared by default
const sprite = tileset.getSpriteForGid(object.gid).clone();
sprite.destSize.width = object.tiledObject.width ?? sprite.width;
sprite.destSize.height = object.tiledObject.height ?? sprite.height;
if (hasTint) {
sprite.tint = tint;
}

newActor.graphics.use(sprite);
newActor.graphics.offset = tileset.tileOffset;

const animation = tileset.getAnimationForGid(object.gid);
if (animation) {
const animationScaled = animation.clone();
animationScaled.scale = scale;
if (!headless) {
// need to clone because we are modify sprite properties, sprites are shared by default
const sprite = tileset.getSpriteForGid(object.gid).clone();
sprite.destSize.width = object.tiledObject.width ?? sprite.width;
sprite.destSize.height = object.tiledObject.height ?? sprite.height;
if (hasTint) {
animationScaled.tint = tint;
sprite.tint = tint;
}
newActor.graphics.use(animationScaled);

newActor.graphics.use(sprite);
newActor.graphics.offset = tileset.tileOffset;

const animation = tileset.getAnimationForGid(object.gid);
if (animation) {
const animationScaled = animation.clone();
animationScaled.scale = scale;
if (hasTint) {
animationScaled.tint = tint;
}
newActor.graphics.use(animationScaled);
newActor.graphics.offset = tileset.tileOffset;
}
}

// insertable tiles have an x, y, width, height, gid
Expand Down
16 changes: 10 additions & 6 deletions src/resource/tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,16 @@ export class TileLayer implements Layer {
}

const tileset = this.resource.getTilesetForTileGid(gid);
let sprite = tileset.getSpriteForGid(gid);
if (hasTint) {
sprite = sprite.clone();
sprite.tint = tint;
const headless = this.resource.headless;

if (!headless) {
let sprite = tileset.getSpriteForGid(gid);
if (hasTint) {
sprite = sprite.clone();
sprite.tint = tint;
}
tile.addGraphic(sprite, { offset: tileset.tileOffset });
}
tile.addGraphic(sprite, { offset: tileset.tileOffset });


// the whole tilemap uses a giant composite collider relative to the Tilemap
Expand All @@ -123,7 +127,7 @@ export class TileLayer implements Layer {
tile.addCollider(collider);
}

let animation = tileset.getAnimationForGid(gid);
let animation = headless ? null : tileset.getAnimationForGid(gid);
if (animation) {
if (hasTint) {
animation = animation.clone();
Expand Down
38 changes: 19 additions & 19 deletions src/resource/tileset-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,27 @@ export class TilesetResource implements Loadable<Tileset> {

if (isTiledTilesetSingleImage(tileset)) {
const imagePath = pathRelativeToBase(this.path, tileset.image, this.pathMap);
const image = this.imageLoader.getOrAdd(imagePath);
if (image) {
this.data = new Tileset({
name: tileset.name,
tiledTileset: tileset,
firstGid: this.firstGid,
image
});
}
const image = this.headless ? undefined : this.imageLoader.getOrAdd(imagePath);
this.data = new Tileset({
name: tileset.name,
tiledTileset: tileset,
firstGid: this.firstGid,
...({ image }),
});
}

if (isTiledTilesetCollectionOfImages(tileset)) {
const tileToImage = new Map<TiledTile, ImageSource>();
const images: ImageSource[] = [];
if (tileset.tiles) {
for (let tile of tileset.tiles) {
if (tile.image) {
const imagePath = pathRelativeToBase(this.path, tile.image, this.pathMap);
const image = this.imageLoader.getOrAdd(imagePath);
tileToImage.set(tile, image);
images.push(image);
const tileToImage = this.headless ? undefined : new Map<TiledTile, ImageSource>();
if (tileToImage) {
const images: ImageSource[] = [];
if (tileset.tiles) {
for (let tile of tileset.tiles) {
if (tile.image) {
const imagePath = pathRelativeToBase(this.path, tile.image, this.pathMap);
const image = this.imageLoader.getOrAdd(imagePath);
tileToImage.set(tile, image);
images.push(image);
}
}
}
}
Expand All @@ -89,7 +89,7 @@ export class TilesetResource implements Loadable<Tileset> {
name: tileset.name,
tiledTileset: tileset,
firstGid: this.firstGid,
tileToImage: tileToImage
...({ tileToImage }),
});
}

Expand Down
61 changes: 33 additions & 28 deletions src/resource/tileset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class Tileset implements Properties {
this.tiledTileset = tiledTileset;
this.firstGid = firstGid;

if (isTiledTilesetSingleImage(tiledTileset) && image) {
if (isTiledTilesetSingleImage(tiledTileset)) {
mapProps(this, tiledTileset.properties);
const spacing = tiledTileset.spacing;
const columns = Math.floor((tiledTileset.imagewidth + spacing) / (tiledTileset.tilewidth + spacing));
Expand All @@ -95,25 +95,27 @@ export class Tileset implements Properties {
this.verticalFlipTransform = AffineMatrix.identity().translate(0, tiledTileset.tileheight).scale(1, -1);
this.diagonalFlipTransform = AffineMatrix.identity().translate(0, 0).rotate(-Math.PI / 2).scale(-1, 1);
this.objectalignment = tiledTileset.objectalignment ?? (this.orientation === 'orthogonal' ? 'bottomleft' : 'bottom');
this.spritesheet = SpriteSheet.fromImageSource({
image,
grid: {
rows,
columns,
spriteWidth: tiledTileset.tilewidth,
spriteHeight: tiledTileset.tileheight
},
spacing: {
originOffset: {
x: tiledTileset.margin ?? 0,
y: tiledTileset.margin ?? 0
if (image) {
this.spritesheet = SpriteSheet.fromImageSource({
image,
grid: {
rows,
columns,
spriteWidth: tiledTileset.tilewidth,
spriteHeight: tiledTileset.tileheight
},
margin: {
x: tiledTileset.spacing ?? 0,
y: tiledTileset.spacing ?? 0
spacing: {
originOffset: {
x: tiledTileset.margin ?? 0,
y: tiledTileset.margin ?? 0
},
margin: {
x: tiledTileset.spacing ?? 0,
y: tiledTileset.spacing ?? 0
}
}
}
});
});
}
this.tileCount = tiledTileset.tilecount;
this.tileWidth = tiledTileset.tilewidth;
this.tileHeight = tiledTileset.tileheight;
Expand All @@ -125,12 +127,13 @@ export class Tileset implements Properties {
this.tiles.push(new Tile({
id: tile.id,
tileset: this,
tiledTile: tile
tiledTile: tile,
...({ image })
}))
}
}
}
if (isTiledTilesetCollectionOfImages(tiledTileset) && tiledTileset.firstgid !== undefined && tileToImage) {
if (isTiledTilesetCollectionOfImages(tiledTileset) && tiledTileset.firstgid !== undefined) {
this.horizontalFlipTransform = AffineMatrix.identity().translate(tiledTileset.tilewidth, 0).scale(-1, 1);
this.verticalFlipTransform = AffineMatrix.identity().translate(0, tiledTileset.tileheight).scale(1, -1);
this.diagonalFlipTransform = AffineMatrix.identity().translate(0, 0).rotate(-Math.PI / 2).scale(-1, 1);
Expand All @@ -145,19 +148,21 @@ export class Tileset implements Properties {
let sprites: Sprite[] = []
if (tiledTileset.tiles) {
for (const tile of tiledTileset.tiles) {
const image = tileToImage.get(tile);
const image = tileToImage?.get(tile);
if (image) {
this.tiles.push(new Tile({
id: tile.id,
tileset: this,
tiledTile: tile,
image
}))
sprites.push(image.toSprite())
}
this.tiles.push(new Tile({
id: tile.id,
tileset: this,
tiledTile: tile,
...({ image })
}))
}
}
this.spritesheet = new SpriteSheet({ sprites });
if (tileToImage) {
this.spritesheet = new SpriteSheet({ sprites });
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ module.exports = {
commonjs2: "excalibur",
amd: "excalibur",
root: "ex"
},
"jsdom": {
commonjs: "JSDOM",
commonjs2: "JSDOM",
amd: "JSDOM",
root: "JSDOM"
}
},
plugins: [
Expand Down

0 comments on commit c1273d0

Please sign in to comment.