webpack打包ts的两种方案对比

一、内容简介

目前大家常用的webpack打包ts主要为两种方案:

  1. ts-loader:将ts转为js,再使用babel将js转为低版本js;
  2. @babel/preset-typescript:它是直接移除TypeScript,转为JS,这使得它的编译速度飞快,并且只需要管理Babel一个编译器就行了。

二、方案对比

首先我们需要安装 webpack、webpack-cli、typescript 随便写一点ts,用于打包测试:

// index.ts

class Student {
  name: string
  age: Number
  constructor(name: string, age: Number) {
    this.name = name
    this.age = age
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`)
  }
}

const testPromise = (): Promise<string> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('1')
    }, 1000)
  })
}

const studentA = new Student('a', 20)
studentA.greet()
testPromise().then(data => {
  console.log('data', data)
})

1.使用方法法对比

1)ts-loader:

先安装ts-loader: npm install ts-loader --save-dev 在项目中配置webpack.config.js:

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './index.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist')
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader'
      }
    ]
  },
  plugins: []
}

然后需要设置tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "sourceMap": true,
    "target": "es5",
    "lib": [
      "es5",
      "dom",
      "es2015.promise"
    ]
  }
}

lib根据具体代码和应用场景还可以配置其他参数:

接下来打包后可以看到打包代码:

实际const、箭头函数之类的已经转成了es5,而 promise可能在低版本浏览器不兼容,那么设置babel将一些不兼容的语法转为es5:

npm install core-js
npm install --save-dev babel-loader @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill

设置.babelrc;

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": "3",
        "useBuiltIns": "usage"
      }
    ]
  ]
}

在webpack.config.js中设置babel-loader处理js

module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: ['babel-loader', 'ts-loader']
      }
    ]
},

再进行打包:

可以看到promse这类的方法已经有了定义,这样不会造成低版本不兼容的问题了,当然如果你已经设置了 高版本的浏览器,这部分代码也不会被打包进来,因为高版本浏览器已经可以直接使用promise。

2)@babel/preset-typescript:

和上面相关bebel的插件基本相同

npm install core-js
npm install --save-dev babel-loader @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
npm install --save-dev @babel/preset-typescript

就多了@babel/preset-typescript webpack.config.js中直接使用了babel-loader:

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './index.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist')
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: ['babel-loader']
      }
    ]
  },
  plugins: []
}

.babelrc中也只多了@babel/preset-typescript

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": "3",
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-typescript"
  ]
}

执行打包,发现bundle.js也是打包了promise。
这里使用@babel/preset-typescript的时候专门删了tsconfig.js,发现依然可以打包成功,但是ts-loader是一定要设置tsconfig.js的,因为我只是做了一下测试,所以删除了,实际项目中还是要设置tsconfig.js。

2.打包时间比较

两种方案中ts代码完全相同:
ts-loader打包时间:

大概 1000ms

@babel/preset-typescript打包时间:

大概500ms 感觉打包速度上提升还是很明显的,打包的大小是大致一样。

3.使用ts封装npm工具包比较

如果我们需要使用ts来开发一些开发工具类的npm包,其实我还是更推荐ts-loader

我们先新建两个项目,然后随便写一点测试代码:

1)ts-loader:

ts-build-loader 为使用ts-loader来开发的npm包:

ts-build-loader:  src/index.ts

export function addFn(a: number, b: number): number {
  return a + b
}

在tsconfig.json中需要配置生成项目声明文件:

{
  "compilerOptions": {
    "module": "commonjs",
    "sourceMap": true,
    "target": "es5",
    "declaration": true, // 将生成.d.ts文件
    "outDir": "./dist",
    "lib": [
      "es5",
      "dom",
      "es2015.promise"
    ]
  },
  "exclude": [
    "./dist"
  ]
}

此时我们可以执行npm run build看下打包后的代码:

一个项目主要方法的js和一个声明文件index.d.ts。
另外如果我们需要使用声明文件在我们使用时提示,你还是需要在package.json里指定主声明文件:

2)@babel/preset-typescript:

ts-build-babel为使用@babel/preset-typescript开发的npm包:

ts-build-babel:  src/index.ts

export function subFn(a: number, b: number): number {
  return a - b
}

执行npm run build

即使我在tsconfig.json中已经设置了declaration也没有项目的声明文件。

最后我们在这两个项目的package.json中设置main为打包后的js:

此时两种不同打包方案的npm包已经简单实现了。

再新建一个项目test-ts-npm用来测试我们的工具是否可用,我们分别在ts-build-loaderts-build-babel中执行npm link,然后在test-ts-npm中分别链接这两个包:npm link ts-build-loader、 npm link ts-build-babel

在项目中简单的安装一下ts,新建一个index.ts文件测试前面写好的两个工具类:

test-ts-npm:  index.ts

import { addFn } from 'ts-build-loader'
import { subFn } from 'ts-build-babel'

let n = addFn(1, 1)

let m = subFn(1, 1)

console.log(n)
console.log(m)

tsc index.ts编译一下ts,再node index.js执行编译后的js:

证明这两个包都是可用的,然后我们就最简单的对比一下用法就可以发现ts-loader开发npm包的优势是很明显的:

  • 1.引入没有报错提示:

当我们使用了ts-loader打包项目时由于已经生成了声明文件,所以引用这里不会报无法找到模块之类的错误;

  • 2.更智能的错误提示:当我们故意写错一个参数时:

很明显ts-loader打包后的代码由于有index.d.ts文件,在我们使用方法不正确时,vscode会提示相应错误信息。在开发过程中,这样的方式显然是更合理的。

最后,还有了几个疑问没有解决:

    1. @babel/preset-typescript 能否通过设置生成项目的声明文件;
    1. 其实打包后的代码可以明显看到:使用 ts-loader打包后的代码量会远大于 @babel/preset-typescript打包的代码,目前还没找到合理的解释和处理方法

希望有哪位大佬知道的话可以评论在下面,如果我找到了相关问题的资料也会第一时间完善和补充。

三、总结

通过上面的两种方案对比:

如果项目为业务逻辑相关,可以考虑使用@babel/preset-typescript,配置更简单,而且打包速度更快一点; 而ts-loader在配置过程中还是遇到了很多坑的,比如tsconfig.json中lib没有设置导致报错,而在转es5实际也是用babel处理es6的方案解决,这样直接使用@babel/preset-typescript感觉会更加清晰。

如果是作为开发相关工具包或组件库,还是更推荐用ts-loader,这种打包后可以自动生成项目声明文件,在其他项目中引入会有更友好的开发体验。