我对此进行了大量研究。
我正在使用 atom、typescript 和 nodejs。
问题是,当您编译 typescript 时,它会搜索路径(要包含的 .ts 文件的路径)。但是,最终编译的 .js 文件没有替换路径。
解决办法:
- 编译ts文件,使用tsconfig中的路径
- 使用脚本替换最终 .js 文件中的路径标记
- 运行节点应用程序
所以本质上,tsconfig 的一部分看起来像这样
"baseUrl": "./app",
"paths" : {
"@GameInstance" : ["model/game/GameInstance"],
"@Map" : ["model/game/map/Map"],
"@MapCreator" : ["model/game/map/creator/MapCreator"],
"@GameLoop" : ["model/game/GameLoop"],
"@Point" : ["model/other/math/geometry/Point"],
"@Rectangle" : ["model/other/math/geometry/Rectangle"],
"@Functions" : ["model/other/Functions"]
}
并考虑 Rectangle.ts 文件
import { Point } from '@Point';
import { Vector } from '@Vector';
/**
* Represents a rectangle.
* You can query it for collisions or whether rectangles are touching
*/
export class Rectangle {
//more code
Rectangle.ts 在哪里
./src/app/model/other/math/geometry/Rectangle/Rectangle.ts
我们跑
tsc
这将编译我们设置的所有 .ts 文件。在那里,路径将在运行时被替换,如果出现错误,运行
tsc --traceResolution > tmp && gedit tmp
并搜索未定义路径包含的字段。您将能够看到替换日志
然后我们就剩下 Rectangle.js(已构建)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var _Point_1 = require("@Point");
//more code
如您所见,@Point 将不存在,我们无法像这样运行节点应用程序。
然而,为此,我创建了一个脚本,它递归地搜索 js 文件并通过上升到根目录然后到目标路径来替换标记。
有 requirePaths.data 文件,您可以在其中定义令牌和路径。
'@GameLoop' : "./app/model/game/GameLoop"
'@GameInstance' : "./app/model/game/GameInstance"
"@Map" : "./app/model/game/map/Map"
"@MapCreator" : "./app/model/game/map/creator/MapCreator"
"@Point" : "./app/model/other/math/geometry/Point"
"@Rectangle" : "./app/model/other/math/geometry/Point"
现在,这不是通用的,它只是我的热门脚本。请注意,该结构是
src
|-app
| |-model
-build
|-src
|-app
|-model
|-test
从技术上讲,src 中的 app/model... 结构只是复制到 src/build
tsc 从 /src/app 获取源代码并编译它。编译结果在 /src/build
然后是substitutePathsInJS.sh 脚本。
这会扫描构建路径,只要找到标记@Rectangle,就会替换它(下面有更多解释......)代码:
#!/bin/bash
function testreqLevel()
{
local srcPath="$1"
local replacingIn="$2"
local expectedLevel=$3
getPathLevel "$replacingIn"
local res=$?
if [ ! $res -eq $expectedLevel ]; then
echo "[-] test $srcPath , $replacingIn FAILED. ($res != $expectedLevel)"
fi
}
function assertreqPath()
{
local input="$1"
local expected="$2"
if [ ! "$input" = "$expected" ]; then
echo "[-] test $expected FAILED"
echo "computed: $input"
echo "expected: $expected"
fi
}
function testGetPathLevel()
{
testreqLevel "./build/src" "./build/src/app/model/game/GameObject.js" 4
testreqLevel "./build/src" "./build/src/file.js" 1
testreqLevel "./build/src" "./build/src/app/model/game/GameObject.js" 4
}
function testGetPathToRoot()
{
local path=$(getPathToRoot "./build/src" "./build/src/app/model/game/GameObject.js")
assertreqPath "$path" "../../../../../"
path=$(getPathToRoot "./" "./server.js")
assertreqPath "$path" "./"
path=$(getPathToRoot "./" "./app/model/game/GameInstance.js")
assertreqPath "$path" "../../../"
}
function err()
{
echo "[-] $1"
}
function getPathLevel()
{
#get rid of starting ./
local input=$(echo "$1" | sed "s/^\.\///")
local contains=$(echo "$input" | grep '/')
if [ -z "$contains" ]; then
return 0
fi
#echo "$input"
local slashInput=$(echo "$input" | awk -F/ '{print NF-1}')
return $(($slashInput - 1))
}
#given ROOT, and PATH, returns a path P such as being in directory PATH, from there using P we get to root
#example:
#ROOT=./src
#PATH=./src/model/game/objects/a.js
#returns ../../
function getPathToRoot()
{
local root="$1"
local input="$2"
getPathLevel "$input"
local level=$?
if [ $level -eq 0 ]; then
echo "./"
return 0
fi
for ((i=1; i <= level + 1; i++)); do
echo -n '../'
done
#echo "$root" | sed 's/^\.\///'
}
function parseData()
{
echo "**************"
echo "**************"
local data="$1"
let lineNum=1
while read -r line; do
parseLine "$line" $lineNum
if [ $? -eq 1 ]; then
return 1
fi
let lineNum++
done <<< "$data"
echo 'Parsing ok'
echo "**************"
echo "**************"
return 0
}
function parseLine()
{
if [[ "$1" =~ ^\#.*$ ]] || [[ "$1" =~ ^\ *$ ]]; then
#comment line
return 0
fi
local line=$(echo "$1" | sed "s/\"/'/g")
let lineNum=$2
local QUOTE=\'
local WORD_IN_QUOTES=$QUOTE[^:$QUOTE]*$QUOTE
if [[ "$line" =~ ^\ *$WORD_IN_QUOTES\ *:\ *$WORD_IN_QUOTES\ *$ ]]; then
# valid key : value pair
local key=$(echo "$line" | awk -F: '{print $1}' | sed 's/^ *//g' \
| sed 's/ *$//g' | sed 's/\//\\\//g' | sed "s/'//g" | sed "s/\./\\\./g")
local val=$(echo "$line" | awk -F: '{print $2}' | sed 's/^ *//g' \
| sed 's/ *$//g' | sed "s/'//g")
echo "[+] Found substitution from '$key' : '$val'"
if [ -z "$REPLACEMENT_KEY_VAL" ]; then
REPLACEMENT_KEY_VAL="$key|$val"
else
REPLACEMENT_KEY_VAL="$REPLACEMENT_KEY_VAL;$key|$val"
fi
else
err "Parse error on line $lineNum"
echo "Expecting lines 'token' : 'value'"
return 1
fi
return 0
}
function replaceInFiles()
{
cd "$WHERE_SUBSTITUTE"
echo "substitution root $WHERE_SUBSTITUTE"
local fileList=`find . -type f -name "*.js" | grep -v "$EXCLUDE"`
echo "$fileList"| while read fname; do
export IFS=";"
echo "Replacing in file '$WHERE_SUBSTITUTE$fname'"
for line in $REPLACEMENT_KEY_VAL; do
local key=`echo "$line" | awk -F\| '{print $1}'`
local val=`echo "$line" | awk -F\| '{print $2}'`
local finalPath=$(getPathToRoot "./" "$fname")"$val"
if [ $VERBOSE -eq 1 ]; then
echo -e "\tsubstitute '$key' => '$val'"
#echo -e "\t$finalPath"
echo -e "\treplacing $key -> $finalPath"
fi
#escape final path for sed
#slashes, dots
finalPath=$(echo "$finalPath" | sed 's/\//\\\//g'| sed 's/\./\\\./g')
if [ $VERBOSE -eq 1 ]; then
echo -e '\t\tsed -i.bak '"s/require(\(.\)$key/require(\1$finalPath/g"\ "$fname"
fi
sed -i.bak "s/require(\(.\)$key\(.\))/require(\1$finalPath\2)/g" "$fname"
done
done
return 0
}
function quit()
{
echo "*************************************"
echo "*****SUBSTITUTING PATHS EXITING******"
echo "*************************************"
echo
exit $1
}
#######################################
CURRENTDIR=`dirname "$(realpath $0)"`
WHERE_SUBSTITUTE='./build/src'
REPLACEMENT_KEY_VAL="";
VERBOSE=0
FILE="$CURRENTDIR/requirePaths.data"
EXCLUDE='./app/view'
if [ "$1" = "-t" ]; then
testGetPathLevel
testGetPathToRoot
echo "[+] tests done"
exit 0
fi
if [ "$1" = "-v" ]; then
VERBOSE=1
fi
echo "*************************************"
echo "********SUBSTITUTING PATHS***********"
echo "*************************************"
if [ ! -f "$FILE" ]; then
err "File $FILE does not exist"
quit 1
fi
DATA=`cat "$FILE"`
parseData "$DATA"
if [ $? -eq 1 ]; then
quit 1
fi
replaceInFiles
quit $?
这似乎令人困惑,但请考虑一下。
我们有 Rectangle.js 文件。
脚本从 requirePaths.data 文件加载一堆输入标记,在这种情况下,让我们专注于行
"@Point" : "./app/model/other/math/geometry/Point"
脚本从 ./src 运行,并被赋予根目录 ./src/build/src
脚本执行 cd ./src/build/src
执行查找。在那里,它将收到
./model/other/math/geometry/Rectangle/Rectangle.ts
那个的绝对路径是
./src/build/src/app/model/other/math/geometry/Rectangle/Rectangle.ts
但我们现在不关心绝对路径。
计算路径,例如他从目录中获取的路径
Whis 的结果类似于
./../../../../
他会喜欢从目录中获取的位置
/src/build/app/model/other/math/geometry/Rectangle
到目录
/src/build/app
然后,在该字符串后面,我们添加从数据文件提供的路径
./../../../.././app/model/other/math/geometry/Point
所以文件 Rectangle.js(在某处的 BUILD 文件夹中)的最终替换是
之前
require("@Point")
之后
require("./../../../.././app/model/other/math/geometry/Point")
这很糟糕,但无论如何我们都不关心 js 中的内容。主要是它有效。
缺点
您不能将它与代码监视器结合使用。监控 tsc 然后,当代码更改完成时,进行自动 tsc 编译,然后自动运行 shell 路径替换,然后在最终的 js 文件上调整 nodeJS 是可能的,但是由于某种原因,sh 脚本替换路径,监控软件认为是代码更改(不知道为什么,它已从监视器中排除)并再次编译。因此,你生成了一个无限循环
您必须手动编译它,一步一步,或者只是在 tsc 编译上使用监视器。当您编写一些代码时,请运行替换并测试 nodeJS 功能。
添加新的食品类时,您必须为其定义一个标记 (@Food) 和 2 个位置的文件路径 (tsconfig) 以及 shell 脚本的输入
您使整个编译过程更长。说实话,tsc 反正占用了大部分时间,而 bash 脚本也没有那么费时出人意料....
使用 mocha 实现测试时,必须再次进行逐步编译,完成后,在最终 js 文件上方运行 mocha。但是为此您可以编写脚本....
有些人通常只替换@app 或某些目录。这样做的问题是,每当您移动源文件时,您都必须进行大量更改...
好的一面
- 当移动文件时,你改变了一个字符串(在两个地方......)
- 不再有使大型项目无法维护的相对路径
- 很有趣,但确实有效,而且我没有遇到大问题(如果使用得当)