获取Typescript 2路径映射(别名)工作

时间:2017-02-16 14:31:07

标签: node.js typescript typescript2.0

我正在尝试使用以下配置为我的打字稿应用程序引用自定义模块快捷方式(即使用ts路径映射功能)。

项目结构

dist/

src/
  lyrics/
     ... ts files
  app/
     ... ts files

完整的项目结构在这里:github.com/adadgio/npm-lyrics-ts,当然没有提交dist文件夹)

tsconfig.json

{
    "compilerOptions": {
        "outDir": "dist",
        "module": "commonjs",
        "target": "es6",
        "sourceMap": true,
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "removeComments": true,
        "noImplicitAny": false,
        "baseUrl": ".",
        "paths": {
            "*": ["src/lyrics/*"], // to much here !! but none work
            "zutils/*": ["./src/lyrics/*", "src/lyrics/utils/*", "dist/lyrics/utils/*"]
        },
        "rootDir": "."
    },
    "exclude": [
        "dist",
        "node_modules"
    ],
    "include": [
        "src/**/*.ts"
    ]
}

当我运行我的npm start / compile或watch脚本时,我没有得到Typescript错误。以下工作(Atom是我的IDE)

// string-utils.ts does exist, no IDE error, typescript DOES compile
`import { StringUtils } from 'zutils/string-utils';` 

但是我接着发现NodeJS错误:

Error: Cannot find module 'zutils/string-utils'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/src/index.ts:7:1)
    at Module._compile (module.js:571:32)
    at Module.m._compile (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/node_modules/ts-node/src/index.ts:413:23)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/node_modules/ts-node/src/index.ts:416:12)
    at Module.load (module.js:488:32)

看起来该模块正试图从node_modules文件夹中解析。我已阅读有关Typescript路径映射的文档,但我无法使其工作。

1 个答案:

答案 0 :(得分:2)

我对此进行了大量研究。 我正在使用原子,打字稿和nodejs。

问题是,当您编译打字稿时,它会搜索路径(要包含的.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

然后,有一个replacePathsInJS.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

但是我们现在不在乎绝对路径。

计算路径,例如他从目录获取的路径 会产生这样的结果

./../../../../

他希望在哪里从目录获取

/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中的内容。最主要的是它有效。

缺点

  1. 您不能将其与代码监视器结合使用。监视tsc,然后在完成代码更改后自动进行tsc编译,然后自动运行shell路径替换,然后对最终的js文件进行tun nodeJS调整,但是由于某种原因,然后用sh脚本替换路径,监视软件认为是代码更改(不知道为什么,它已将其构建从监视器中排除)并再次编译。因此,您生成了无限循环

  2. 您必须手动,分步进行编译,或者仅在tsc编译时使用Monitor。当您编写一些代码时,请运行替换并测试nodeJS功能。

  3. 添加新的Class Food时,必须为其定义一个令牌(@Food),并在两个位置(tsconfig)定义文件路径,并输入Shell脚本

  4. 您可以使整个编译过程更长。坦白地说,tsc会花费大部分时间,bash脚本并没有那么费时。...

  5. 使用mocha进行测试时,您必须再次进行逐步编译,完成后,在最终js文件上方运行mocha。但是为此,您可以编写脚本....

某些人通常只替换@app或某些目录。这样做的问题是,每当您移动源文件时,都必须进行很多更改...

好的一面

  1. 在移动文件时,更改一个字符串(在两个位置...。)
  2. 没有更多的相对路径使大型项目无法维护
  3. 这很有趣,但是它确实有效,而且我没有遇到重大问题(如果使用正确)