环境配置

基础配置

概要

  • ts-node
  • tsc
  • t s config.json
  • vscode
  • 和 webpack 搭配使用

ts-node

Node 环境的 typescript 解释执行器。REPL(Read eval print loop)。

npm i -g ts-node
# yarn global add ts-node
bash

使用 ts-node 执行文件

ts-node smoe.ts
bash

配置文件

tsconfig.json

{
  "compilerOptions": {}
}
json

tsc

一个 ts 的编译器。

npm i -g tsc
# yarn global add tsc
bash

可以指定编译某个 ts 文件:

tsc hello.ts
bash

也可以通过 tsconfig.json 配置。

  • 可以用 outDir 配置项配置 js 文件输出的位置;
  • tsc 作为一个指令,可以用 --help 查看用法;
  • 可以用 module 指定生成模块的类型。

当存在 tsconfig.json 时,可以在项目根目录下可以直接运行 tsc 命令对文件进行编译。

{
  "compilerOptoins": {
    "outDir": "dist"
  }
}
json

与 webpack 搭配使用

初始化项目

mkdir ts-webpack
cd ts-webpack
npm init -y
bash

安装依赖

npm install webpack webpack-cli ts-loader typescript --save-dev
# yarn add webpack webpack-cli ts-loader typescript 
bash

编写测试文件

// src/index.ts

export class TreeNode<T> {
  left: TreeNode<T>
  right: TreeNode<T>
  data: T

  constructor(data: T) {
    this.data = data
  }
}

function log(x) {
  console.log(x)
}

const node = new TreeNode<number>(100)

log(node.data)
typescript

编写 webpack 配置文件

// webpack.config.js

const path = require('path')

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

编写 npm 脚本

{
  "name": "ts-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start:dev": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "ts-loader": "^9.4.2",
    "typescript": "^4.9.5",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  }
}
json

运行测试脚本

yarn start:dev
node dist/bundle.index.js
bash

进阶配置

概要

  • react + ts-loader 配置
  • react + babel 配置
  • vue + vue-loader 配置
  • vue + babel 配置

核心问题:preset 的顺序有关系吗?preset 的作用是什么?

react + ts-loader

安装 npm 包

pnpm i react react-dom
bash
pnpm i @types/react @types/react-dom --save-dev
bash
pnpm i awesome-typescript-loader --save-dev
bash

ts-loader 已经安装过,这里不再安装。

pnpm i webpack-dev-server html-webpack-plugin -D
bash

编写 react 文件

// src/ReactHello.tsx

import React from 'react'
import ReactDOM from 'react-dom'

const App: () => JSX.Element = () => {
  return (
    <div>
      <h1>Hello React!</h1>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

编写 tsconfig.json 文件

{
  "compilerOptions": {
    "esModuleInterop": true, // 支持多套编码风格
    "jsx": "react" // react, div => React.createElement
  }
}
json

编写 template 模板文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
html

编写 webpack.react.js 文件

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    index: './src/ReactHello.tsx'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.tsx?/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  output: {
    filename: 'bundle.[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    static: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'template.html')
    })
  ]
}
js

编写测试脚本

"scripts": {
  "start:dev": "webpack",
  "start:react": "webpack --config webpack.react.js"
}
bash

运行测试脚本

yarn start:react
bash

这样运行命令并不会启动开发服务器,我们需要修改 scripts 脚本。

"scripts": {
  "start:dev": "webpack",
  "start:react": "webpack server --config webpack.react.js"
}
bash

重新运行脚本,就可以看到开发服务器被启动,可以正常访问。

react + babel preset

babel-preset 和 ts-loader 的区别是什么?

  • babel 作用

    • The Compiler for next generation Javascript
    • 所有编译 JS 的事情,babel 都干
    • es6 => es5 => es3 => polyfill
  • 缓存 + 优化

  • 插件 + 生态

两者本质区别不大,不过从生态和优化上来看,babel 更优。

babel-loader 背后是 babel,ts-loader 背后是 tsc 编译器。

pnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --save-dev
bash

Babel 有两套机制,一套是 preset,一套是 plugin。我们可以认为 preset 是 plugin 的集合,其实就是预选项的概念。

编写 webpack.react.withbabel.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
	// ...
  module: {
    rules: [
      {
        test: /\.tsx?/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-typescript',
              '@babel/preset-react', // 不使用 ts-loader 的情况下,需要使用
              '@babel/preset-env'
            ]
          }
        },
        exclude: /node_modules/
      }
    ]
  },
	// ...
}
js

编写 scripts 脚本

{
   "scripts": {
    "start:dev": "webpack",
    "start:react": "webpack server --config webpack.react.js",
    "start:babel": "webpack server --config webpack.react.withbabel.js"
  }
}
json

vue + loader

增加 vue 依赖

pnpm i vue@next -D
pnpm i @vue/compiler-sfc -D

pnpm i vue-loader -D
bash

sfc:Single File Component,单文件组件

编写 vue sfc 及 bootstraper

<template>
  <h1>Hello Vue!</h1>
</template>

<script lang="ts">
export default {
  setup() {
    return {}
  }
}
</script>
// src/main.ts

import { createApp } from 'vue'
import HelloVue from './VueHello.vue'

createApp(HelloVue).mount('#root')
typescript
// src/shims-vue.d.ts

/* eslint-disable */
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
typescript

vue 需要增加 shims-vue.d.ts 文件,否则 main.ts 中引入 .vue 文件会报错。

shim(垫片),通常为了处理兼容而命名。上面这个 shim 的目标是让 vscode 和 webpack 知道 .vue 的文件可以被当作一个组件定义文件来使用。

编写 webpack.vue.js 文件

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  entry: {
    index: './src/main.ts'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.tsx?/,
        loader: 'ts-loader',
        options: {
          // 无法识别 vue 转换的 ts 文件,需要配置该规则添加后缀
          appendTsSuffixTo: [/\.vue$/]
        },
        exclude: /node_modules/
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  output: {
    filename: 'bundle.[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    static: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'template.html')
    }),
    new VueLoaderPlugin()
  ]
}
js

编写 script 脚本

{
  "scripts": {
    "start:dev": "webpack",
    "start:react": "webpack server --config webpack.react.js",
    "start:babel": "webpack server --config webpack.react.withbabel.js",
    "start:vue": "webpack server --config webpack.vue.js"
  }
}
json

vue + babel preset

安装依赖

pnpm i babel-preset-typescript-vue3 -D
bash

编写 webpack.vue.withbabel.js 文件

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  entry: {
    index: './src/main.ts'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.tsx?/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', 'babel-preset-typescript-vue3', '@babel/preset-typescript']
        },
        exclude: /node_modules/
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  output: {
    filename: 'bundle.[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    static: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'template.html')
    }),
    new VueLoaderPlugin()
  ]
}
js

编写 script 脚本文件

{
  "scripts": {
    "start:dev": "webpack",
    "start:react": "webpack server --config webpack.react.js",
    "start:react-babel": "webpack server --config webpack.react.withbabel.js",
    "start:vue": "webpack server --config webpack.vue.js",
    "start:vue-babel": "webpack server --config webpack.vue.withbabel.js"
  }
}
json

技术拓展

Babel preset 执行顺序有关系吗?

存在顺序关系,真正执行时可能存在多种执行顺序。

preset-react 和 preset-vue 的作用是什么?

@babel/preset-react:集成更多的 plugin,让我们可以使用,其实就是一个 plugin 包。

Preset-typescript-vue3 只增加了 typescript 的转换能力。

实战建议

实际开发时,还是推荐使用官方脚手架 react-create- app、vue 或者 vite。

我们学习环境配置只是作为架构师的一项基本技能。当你架构项目时,我们需要的是一个成熟的脚手架,推荐使用第三方的工具。