vue3 + webpack5 配置TS项目

3,029 阅读2分钟

webpack新特性

  • 持久化缓存
  • moduleIds & chunkIds 的优化
  • 更智能的 tree shaking
  • Module Federation
  • ...

Vue3 的新特性

  • 更小
  • 更快
  • 加强 TypeScript 支持
  • 加强 API 设计一致性
  • 提高自身可维护性
  • 开放更多底层功能

开始配置

创建项目

npm init -y

安装所需插件及必要配置

  1. 安装webppack5
npm i webpack webpack-cli webpack-dev-server -D 
  1. css 解析
npm i less less-loader css-loader style-loader -D 
  1. vue-loader
npm i vue-loader@next @vue/compiler-sfc -D 

src 文件夹下添加 shims-vue.d.ts 文件,解决 vue 类型报错

declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}
  1. 模板解析
npm i html-webpack-plugin -D
  1. 安装 typescript 及解析插件
npm i typescript ts-loader --save-dev
  1. 处理图片url-loader
npm i url-loader -D
  1. 处理css,less
cnpm i mini-css-extract-plugin css-loader less less-loader postcss postcss-loader -D

配置如webpack.base.config.js所示

  1. 处理ts
npm i typescript ts-loader --save-dev

ts-loader 为单进程执行类型检查和转译,因此效率有些慢,可以用多进程方案:即关闭ts-loader的类型检查,类型检查由 fork-ts-checker-webpack-plugin 插件执行。npm i fork-ts-checker-webpack-plugin --save-dev

  1. 其他插件 合并插件配置webpack-merge

删除build生成的dist包clean-webpack-plugin

跨平台cross-env

创建配置文件,并写入配置(webpack4与webpack5配置基本一样)

创建build文件夹,并依次创建公共配置文件webpack.base.config.js,开发配置文件webpack.dev.config.js,生产配置文件webpack.prod.config.js

webpack.base.config.js

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {VueLoaderPlugin} = require('vue-loader')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const path = require('path')
const Dotenv = require('dotenv-webpack')

function getConfigPath(mode) {
  return path.resolve(process.cwd(), `.env.${mode}`)
}
module.exports = {
    entry: './src/main.ts',
    cache: {
      type: 'filesystem'  // 持久化缓存
    },
    output: {
        filename: 'js/[name].[chunkhash:5].js',
        path: path.resolve(__dirname, "../dist")
      },
    module: {
        rules: [
          {
            test: /\.(t|j)s$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
              },
            ],
          },
          {
              test: /\.(t|j)s$/,
              exclude: /node_modules/,
              use: [
                {
                  loader: 'ts-loader',
                  options: {
                    // 指定特定的ts编译配置,为了区分脚本的ts配置
                    configFile: path.resolve(__dirname, '../tsconfig.json'),
                    // 对应文件添加个.ts或.tsx后缀
                    appendTsSuffixTo: [/\.vue$/],
                    transpileOnly: true // 关闭类型检测,即值进行转译
                  },
                },
              ],
          },
          {
            test: /\.css$/,
            use: [
              // 'vue-style-loader',
              // 'style-loader',
              MiniCssExtractPlugin.loader,
              'postcss-loader',
              'css-loader'
            ],
          },
          {
            test: /\.less$/,
            use: [
              // 'vue-style-loader',
              MiniCssExtractPlugin.loader,
              // 'style-loader',
              'css-loader',
              'postcss-loader',
              'less-loader'
            ],
          },
          {
            test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 1024,
                },
              }
            ]
          }
        ]
    },
    optimization: {
      splitChunks: {
        chunks: 'async',
        minSize: 20000,
        minChunks: 1, // 最小使用的次数 
        maxAsyncRequests: 5,
        maxInitialRequests: 3,
        minChunks: 1,
        cacheGroups: {
          // 提取公共js
          commons: {
            chunks: "all", // initial
            minChunks: 2,
            maxInitialRequests: 5,
            minSize: 0,
            name: "commons"
          }
        }
      }
    },
    resolve: {
      extensions: [".ts", ".tsx", ".js", ".json"],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            title: 'webpack5+Vue3'
        }),
        new VueLoaderPlugin(),
        new MiniCssExtractPlugin({
          filename: "style/[name].[hash:8].css",
          chunkFilename: "style/[hash:8].css"
        }),
        new ForkTsCheckerWebpackPlugin(),
    ]
}

webpack.dev.config.js

const { merge } = require('webpack-merge')
const common = require('./webpack.base.config')
// const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
// const smp = new SpeedMeasurePlugin()
module.exports = merge(common,{
    mode: 'development',
    devtool: 'source-map',
    devServer: {
        port: 9091, // 本地服务器端口号
        // hot: true, // 热重载
        // overlay: true // 如果代码出错,会在浏览器页面弹出“浮动层”。类似于 vue-cli 等脚手架
        disableHostCheck: true
    }
})

webpack.prod.config.js

const { merge } = require('webpack-merge')
const common = require('./webpack.base.config')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = merge(common,{
    mode: 'production',
    plugins: [
        new CleanWebpackPlugin()
    ]
})

配置脚本

package.json

...
"scripts": {
    "server": "cross-env NODE_ENV=development webpack serve  --progress --hot --inline --config build/webpack.dev.config.js",
    "build": "cross-env NODE_ENV=production webpack --mode=production --config build/webpack.prod.config.js"
  },
...

4.配置tsconfig.json

{
    "compilerOptions": {
      "target": "esnext",
      "module": "esnext",
      "strict": true,
      "jsx": "preserve",
      "importHelpers": true,
      "moduleResolution": "node",
      "experimentalDecorators": true,
      "skipLibCheck": true,
      "esModuleInterop": true,
      "allowSyntheticDefaultImports": true,
      "sourceMap": true,
      "baseUrl": ".",
      "types": [
        "webpack-env",
        "node"
      ],
      "paths": {
        "@/*": [
          "src/*"
        ]
      },
      "lib": [
        "esnext",
        "dom",
        "dom.iterable",
        "scripthost"
      ]
    },
    "include": [
      "src/**/*.ts",
      "src/**/*.tsx",
      "src/**/*.vue"
    ],
    "exclude": [
      "node_modules"
    ]
  }

到这里配置的项目已经可以运行了,但是还有几个问题: Ts 可以编译为指定版本的 js,那么还需要 babel 么?

tsc 的 target 只转译语法,不集成 polyfill,所以还是得要 babel。

比如把箭头函数转成普通 function、aysnc + await 变成 Promise.then,这是语法转译;

但你运行环境里如果没有 Promise.prototype.finally,那没有就还是没有。

因此我们项目里还是需要 babel.

Webpack 转译 Typescript 现有方案:

方案123
单进程方案(类型检查和转译在同一个进程)ts-loader(transplieOnly为false)awesome-typescript-loader
多进程方案ts-loader(transplieOnly为true+fork-ts-checker-webpack-plugin)awesome-typescript-loader+自带的Checkpluginbabel+fork-ts-checker-webpack-plugin

综合考虑性能和扩展性,目前比较推荐的是 babel+fork-ts-checker-webpack-plugin 方案。

在 babel7 之前,是需要同时使用 ts-loader 和 babel-loader 的,其编译过程 TS > TS 编译器 > JS > Babel > JS 。可见编译了两次js,效率有些低下。但是 babel7 出来之后有了解析 typescript 的能力,有了这一层面的支持,我们就可以只使用 babel,而不用再加一轮 ts 的编译流程了。

在 babel 7 中,我们使用新的 @babel/preset-typescript 预设,结合一些插件便可以解析大部分的 ts 语法。

那么,Babel 是如何处理 TypeScript 代码的呢?

Babel 删除了所有 TypeScript,将其转换为常规的 JavaScript,并继续以它自己的方式处理。删除了 typescript 则不需要进行类型检查,不会有烦人的类型错误提醒,因此编译速度提升.

添加 babel 解析 typescript

# 安装以下依赖 --save-dev
# webpack loader
babel-loader
# babel 核心
@babel/core
# 智能转换成目标运行环境代码
@babel/preset-env
# 解析 typescript 的 babel 预设
@babel/preset-typescript
# polyfill 
@babel/plugin-transform-runtime
# 支持 ts 类的写法
@babel/plugin-proposal-class-properties 

# 安装以下依赖 --save
@babel/runtime
@babel/runtime-corejs3
"core-js": "^3.11.0"

删除 ts-loader, 添加 babel-loader

{
    test: /\.(t|j)s$/,
    exclude: /node_modules/,
    use: [
      {
        loader: 'babel-loader',
      },
    ],
}

项目根目录添加 babel 配置文件 babel.config.js

module.exports = {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage', // 按需引入 polyfill
          corejs: 3,
        },
      ],
      [
        '@babel/preset-typescript', // 引用Typescript插件
        {
          allExtensions: true, // 支持所有文件扩展名,否则在vue文件中使用ts会报错
        },
      ],
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          corejs: 3,
        },
      ],
      '@babel/proposal-class-properties'
      // '@babel/proposal-object-rest-spread',
    ],
  }

思考:vue-cli自动创建的脚手架项目可以根据.env.production,.env.development读取配置,应该如何实现?

思路:可以使用dotenv-webpack,并在执行脚本前指定其运行环境cross-env NODE_ENV=development,使用process.env.NODE_ENV获取即可实现。

其他插件: speed-measure-webpack-plugin 打包速度分析 webpack-bundle-analyzer 打包结果分析 ... 当然还有很多其他的插件。

最后附上代码:地址