首页 文章

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

提问于
浏览
0

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

Project structure

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 回答

  • 1

    我对此做了很多研究 . 我正在使用atom,typescript和nodejs .

    问题是,当你编译typescript时,它会搜索路径(要包含的.ts文件的路径) . 但是,最终编译的.js文件不会替换路径 .

    The solution:

    • 编译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
    

    并且搜索fiel wehere是未定义的路径包括 . 您将能够看到替换日志

    然后我们留下了Rectangle.js(建成)

    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var _Point_1 = require("@Point");
    //more code
    

    如您所见,@ Point将不存在,我们无法像这样运行节点应用程序 .

    然而,为此,我创建了一个脚本,递归搜索js文件并通过上升到root然后到目标路径来替换令牌 .

    有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脚本 . 这个scand用于构建路径,每当它找到令牌@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文件夹中的某个地方)是

    before

    require("@Point")
    

    after

    require("./../../../.././app/model/other/math/geometry/Point")
    

    哪个很糟糕,但我们并不关心js中的内容 . 主要是它有效 .

    Drawbacks

    • 您无法将其与代码监视器结合使用 . 监视tsc,然后,当代码更改完成后,执行自动tsc编译,然后自动运行shell路径替换,然后在最终的js文件上ping nodeJS,但由于某种原因,然后sh脚本替换路径,监视软件考虑是代码中的变化(不知道为什么,它已经从监视器中排除了构建)并再次编译 . 因此,你产生一个无限循环

    • 您必须手动编译,一步一步,或者只是在tsc编译中使用monitor . 编写代码时,请运行替换并测试nodeJS功能 .

    • 添加新的Class Food时,必须为它定义一个标记(@Food)和2个位置的文件路径(tsconfig)以及shell脚本的输入

    • 使整个编译过程更长 . 老实说,无论如何,tsc占用了大部分时间,并且bash脚本并不是那么耗时......

    • 使用mocha实现测试时,必须再次进行逐步编译,完成后,在最终的js文件上运行mocha . 但为此你可以编写脚本....

    有些人通常只会替换@app或某些目录 . 问题是,无论何时移动源文件,都必须进行大量更改......

    The good sides

    • 当移动文件时,你改变一个字符串(在两个地方....)

    • 没有更多的相对路径使大项目无法维护

    • 这很有趣,但确实有效,我没有遇到重大问题(如果使用得当)

相关问题