简体   繁体   English

使用node.js正确的模块组织

[英]correct module organization with node.js

I've built a small chess game in plain JavaScript, and in order to handle imports I just pushed them all into the html file, giving me the following: 我用普通的JavaScript构建了一个小型国际象棋游戏,为了处理导入,我将它们全部推送到html文件中,并提供了以下内容:

game.html game.html

.
.
<body id="body">
    <script src="scripts/utils.js"></script>
    <script src="scripts/pieces.js"></script>
    .
    .
    .
    <script src="scripts/game.js"></script>
</body>
</html>

Now I am starting to use Node.js and want to move the logic server-side and structure it properly using imports, but there are two files I'm having trouble with. 现在,我开始使用Node.js,并希望移动逻辑服务器端并使用导入对其进行适当的结构化,但是我遇到了两个问题。 The first one is 第一个是

utils.js utils.js

function getEnemy(player) {
    return player===WHITE? BLACK : WHITE;
}

/**
 * A generator to generate pieces from given fen and return them all in an array.
 */
function generatePieces(fen) {

    var piece_array = [];
    var ranks = fen.split(' ')[0].split('/');
    for (var i=0; i < ranks.length; i++) {

        var squares = ranks[i];
        var rank = 8 - i;
        var file = 1; // keeping track of the current file.
        for (var j=0; j < squares.length; j++) {
            if (Number(squares[j])) {
                file += Number(squares[j]);
                continue;
            }
            var color = (squares[j] === squares[j].toUpperCase()? WHITE : BLACK);
            piece_array.push( generatePiece(squares[j], color, file, rank));
            file +=1;
        }
    }
    return piece_array;
}
function generatePiece(type, color, file, rank) {

    var square = new Square(file, rank);
    type = type.toUpperCase();
    switch(type) {
        case PAWN:
            return new Pawn(square, color);
        case KNIGHT:
            return new Knight(square, color);
        case KING:
            return new King(square, color);
        case BISHOP:
            return new Bishop(square, color);
        case ROOK:
            return new Rook(square, color);
        case QUEEN:
            return new Queen(square, color);
    }
}

/**
 * generates a square from the first two numbers in an array.
 * no validations are made.
 * @param position_array an array whose first two elements are numbers.
 * @returns {Square} a square with file equal to the first element and rank the second.
 */
function getSquareFromArray(position_array) {
    return new Square(position_array[0], position_array[1]);
}

/******* Square class
 * square object to encapsulate all these ugly arrays.
 * @param file file of the new square.
 * @param rank rank of the new square.
 */
function Square(file, rank) {
    this.file = file;
    this.rank = rank;

    /**
     * Checks if two squares have the same file and rank.
     * @param other another square
     * @returns {boolean} true if both squares are of the same position on the board.
     */
    this.equals = function(other) {
       return (this.file === other.file && this.rank === other.rank);
    };

    /**
     * Returns a string version of the square, formatted as "file rank".
     * @returns {string} string version of the square.
     */
    this.toString = function() {
        return String(file) + ' ' + String(rank);
    };


    /**
     * returns a new square with the given file and rank added to this square's file and rank.
     * @param file file to move the square
     * @param rank rank to move the square
     * @returns {Square}
     */
    this.getSquareAtOffset = function(file, rank) {
        file = Number(file)? file : 0;
        rank = Number(rank)? rank : 0;
        return new Square(this.file + file, this.rank + rank);
    };
}

/**
 * creates a new square object from a string of format '%d $d'
 * @param string string in format '%d %d'
 * returns square object
 */
function getSquareFromKey(string) {
    values = string.split(' ');
    return new Square(Number(values[0]), Number(values[1]));
}
/******* Move object
 * Move object to encapsulate a single game move.
 * Might be a bit of an overkill, but I prefer an array of moves over an array of arrays of squares.
 * @param from A square to move from.
 * @param to A square to move to.
 */
function Move(from, to) {
    this.from = from;
    this.to = to;
}

/******* SpecialMove object
 * Move object to encapsulate a single game move.
 * Might be a bit of an overkill, but I prefer an array of moves over an array of arrays of squares.
 * @param moves array of moves to make
 * @param removes array of squares to remove pieces from
 * @param insertions array of triplets, square to insert, wanted piece type, and piece color.
 */
function SpecialMove(moves, removes, insertions) {
    this.moves = moves? moves : [];
    this.removes = removes? removes : [];
    this.insertions = insertions? insertions : [];
}

Which I'm not sure how to properly export. 我不确定如何正确导出。 If I add each function/object individually to module.exports then for each of my objects using this module (which is pretty much any other object) I'll need to do something like this.utils = require('./utils'); 如果我将每个函数/对象分别添加到module.exports中,则使用该模块为我的每个对象(几乎是其他任何对象),我都需要这样做this.utils = require('./utils'); and then call, for example, square = new this.utils.Square(file, rank); 然后调用例如square = new this.utils.Square(file, rank); , which doesn't seem like all that good of a solution. ,这似乎并不是解决方案的全部优点。 Is there a nicer way of importing this file pretty much globally? 有没有更好的方法可以在全球范围内导入此文件?

The second is a list of objects for the different types of pieces pieces.js 第二个是不同类型碎片的对象列表pieces.js

var WHITE = 'w';
var BLACK = 'b';

var PAWN = 'P';
var KNIGHT = 'N';
var KING = 'K';
var BISHOP = 'B';
var ROOK = 'R';
var QUEEN = 'Q';

/********* Piece class
 * The parent class of all piece types.
 * @param square square of the piece.
 * @param color color of the piece, should be either 'white' or 'black'.
 * @constructor
 */
function Piece(square, color) {
    this.square = square;
    this.color = color;
}

/**
 * Returns the path the piece needs in order to get to the given square, if it's not a capture.
 * Returns null if the piece can't reach the square in one move.
 * @param to target square.
 */

Piece.prototype.getPath = function(to) {
    return null;
};

/**
 * Returns the path the piece needs in order to capture in the given square.
 * Returns null if the piece can't reach the square in one move.
 * Implement this if the piece captures in a different way then moving (i.e. a pawn);
 * @param to target square.
 */

Piece.prototype.getCapturePath = function(to) {
    return this.getPath(to);
};

/**
 * Compares the square of the piece with given square
 * @returns {boolean} if the piece is at the given square.
 */

Piece.prototype.isAt= function(square) {
  return (this.square.equals(square));
};

/******* Pawn class
 * Holds the movement of the pawn, refer to Piece for explanations.
 * @type {Piece}
 */
Pawn.prototype = Object.create(Piece.prototype);
Pawn.prototype.constructor = Pawn;
function Pawn(square, color) {
    Piece.call(this, square, color);
    this.startPosition = (color === WHITE? 2 : 7);
    this.direction = (color === WHITE? 1 : -1);

    this.type = PAWN;
}

.
.
.

/******* Knight class
 * Holds the movement of the Knight, refer to Piece for explanations.
 * @type {Piece}
 */
Knight.prototype = Object.create(Piece.prototype);
Knight.prototype.constructor = Knight;
function Knight(square, color) {
    Piece.call(this, square, color);

    this.type = KNIGHT;
} 
.
.
.
/******* King class
 * Holds the movement of the King, refer to Piece for explanations.
 * @type {Piece}
 */
King.prototype = Object.create(Piece.prototype);
King.prototype.constructor = King;
function King(square, color, has_moved) {
    Piece.call(this, square, color);

    this.type = KING;
}
.
.
.

Where I'm not sure how to correctly import all different "classes" for use in other places. 我不确定如何正确导入所有其他“类”以在其他地方使用。 I thought making the generatePiece function from util.js the module.export of this file, thus turning it into a sort of "piece generator". 我以为可以从util.js中将generatePiece函数module.export该文件的module.export ,从而将其转变为一种“生成器”。 is that a good idea? 这是一个好主意吗?

Unfortunately, there doesn't seem to be a defacto way of doing things in node.js, I'll try in give a couple generalized solutions: 不幸的是,在node.js中似乎没有实际的处理方法,我将尝试给出一些通用的解决方案:

I thought making the generatePiece function from util.js the module.export of this file, thus turning it into a sort of "piece generator". 我认为可以从util.js中将generatePiece函数制作为该文件的module.export,从而将其转变为一种“生成器”。 is that a good idea? 这是一个好主意吗?

I think that using your modules as standalone utils is a very straightforward and elegant solution. 我认为将模块用作独立的utils是一个非常简单明了的解决方案。 It is easily unittestable and is constantly used in node.js projects. 它易于进行单元测试,并在node.js项目中不断使用。

. If I add each function/object individually to module.exports then for each of my objects using this module (which is pretty much any other object) I'll need to do something like this.utils = require('./utils'); 如果我将每个函数/对象分别添加到module.exports中,则使用该模块为我的每个对象(几乎是任何其他对象),我都需要这样做。utils = require('./ utils') ; and then call, for example, square = new this.utils.Square(file, rank);, which doesn't seem like all that good of a solution. 然后调用例如square = new this.utils.Square(file,rank);,这似乎并不是解决方案的全部优点。 Is there a nicer way of importing this file pretty much globally? 有没有更好的方法可以在全球范围内导入此文件?

Instead of storing a require d library as a property in your object you could just construct new squares through the global reference, which is commonly used: 除了可以将require d库作为属性存储在对象中之外,您还可以通过常用的全局引用构造新的正方形:

utils = require('./utils'); and then call, for example, utils = require('./utils'); and then call, for example, square = new utils.Square(file, rank);` utils = require('./utils'); and then call, for example, square = new utils.Square(file,rank);`

Or even subdivide your modules even further, maybe even create a pieces module, or a board module. 甚至可以进一步细分您的模块,甚至可以创建部件模块或板模块。

I try to create as many modules as make sense. 我尝试创建尽可能多的模块。 One benefit is that there are lots of libraries in node to mock along require s. 好处之一是节点中有很多库可以模拟require If you end up doing IO it could be important to mock that for your unittests. 如果您最终要进行IO,那么对单元测试进行模拟可能很重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM