TypeScript 用 Webpack/ts-node 运行的配置记录

摘要:Node 里面去跑,具体场景一些路由配置, 比较大的一块 JSON 数据定义在 TypeScript 里,我另外有增加脚本, 基于这些 JSON 数据用来生成切换路由的函数,这就需要运行 TypeScript 了, 而且可能包含一些额外的业务代码。

公司项目代码是用 TypeScript 写的, 中间遇到有些代码不要放到 Node 里面去跑,具体场景一些路由配置, 比较大的一块 JSON 数据定义在 TypeScript 里,我另外有增加脚本, 基于这些 JSON 数据用来生成切换路由的函数,这就需要运行 TypeScript 了, 而且可能包含一些额外的业务代码。

首先 Node 运行 TypeScript 有提供 ts-node 用来处理,ts-node 会先编译 TypeScript 代码到 JavaScript, 再调用 Node 运行,不过这个办法有一些问题, 一个是 TypeScript 定义的路径配置不成功,另一个问题更麻烦点, 就是引用到的其他的浏览器端代码因为触发运行而引起报错。


Webpack 打包 TypeScript Node 代码

我先想到了一个相对省事的方案, 就是用 Webpack 对 TypeScript 进行打包,打包完成以后输出 JavaScript 代码. 而浏览器代码打包进去, 但不一定运行,由于 TypeScript 配置在 Webpack 当中引用有比较成熟的方案, 整个配置也写好:

module.exports = {
  mode: "development",
  target: "node",
  entry: ["./example/gen-router.ts"],
  output: {
    filename: "gen-router.js",
    path: path.join(__dirname, "../", distFolder),
  },
  devtool: "none",
  module: {
    rules: [
      // 正常的 TypeScript 编译方式, 我这份是拷贝的.
      {
        test: /\.tsx?$/,
        exclude: [/node_modules/, path.join(__dirname, "scripts")],
        use: [
          { loader: "cache-loader" },
          {
            loader: "thread-loader",
            options: {
              workers: require("os").cpus().length - 1,
            },
          },
          {
            loader: "ts-loader",
            options: {
              happyPackMode: true,
              transpileOnly: true,
            },
          },
        ],
      },
    ],
  },
  // Node 模块, 写在 external 里面表明不需要进行打包. 注意 commonjs 前缀
  externals: {
    prettier: "commonjs prettier",
    "@jimengio/router-code-generator": "commonjs @jimengio/router-code-generator",
    fs: "commonjs fs",
    path: "commonjs path",
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
    modules: [path.join(__dirname, "example"), "node_modules"],

    // 引用 Plugin 用于读取 tsconfig.json 文件的配置
    plugins: [new TsconfigPathsPlugin({ configFile: path.join(__dirname, "../tsconfig.json") })],
  },
};

基于这个配置打包以后, TypeScript 的代码被打包好, 并且引用响应的 Node 模块,运行就满足需求了。

这个方式对于其他的服务端渲染的 TypeScript 代码打包也是类似的,一些特殊的依赖如果不好处理, 可以放在 Webpack 当中进行打包和映射, 得到 js.


ts-node 运行

Webpack 配置相对直接运行 TypeScript 来说会复杂一点, 所以还是 ts-node 简单,在依赖少的项目当中, 我改成了用 ts-node 来进行编译运行. 配置如下

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "sourceMap": true,
    "noImplicitAny": false,
    "noImplicitThis": true,
    "strictNullChecks": false,
    "moduleResolution": "node",

    // Node 当前还没有支持直接运行 import/export 语法, 需要编译到 CommonJS
    "module": "commonjs",

    "target": "es2016",
    "jsx": "react",
    "lib": ["es2016"],
    "types": ["node"],
    "baseUrl": "./example/",
    "paths": {
      "models": ["./example/models"]
    },
    "plugins": []
  }
}

其实主要修改就 commonjs 那一行, 然后就是加上参数运行

ts-node -P tsconfig-node.json -r tsconfig-paths/register example/gen-router.ts

注意命令当中的 tsconfig-paths. 这里的 -r 是指定 register. ts-node 是先进行编译再运行的, 但是引用的路径没有全都替换掉. 比如我在 tsconfig.json 里设置了 baseUrl 然后内部引用是简写的, a/b/c, 拿到 Node 本身去运行的时候是不知道这个 a/b/c 对应到哪里, 所以 tsconfig-paths/register 就提供 Node 运行时的方案, 动态查找依赖. 至少这样 Node register 改写以后, 查找模块就能正确进行了.


其他

另外 TypeScript 编译 import 语法时会产生一个 .default 属性. 对于 CommonJS 的模块, 这个 .default 属性是多余的. 所以引用的写法要做调整.

import * as fs from "fs";
import * as path from "path";
import * as prettier from "prettier";

这个可能跟 tsconfig.json 里其他的配置有关联, 我没继续深挖.

整体的代码参考 https://github.com/jimengio/meson-form/pull/62/files


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_5848