👉用Webpack从零搭建一个 Vue3 + TypeScript 开发环境

1,847 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

前言

如今我们开发Vue项目,一般都是直接使用Vite进行项目初始化,如果你是一个习惯于使用Vue CLI创建项目的小伙伴,那么当你现在再去打开Vue CLI官网时,能够看到这样一句话

image.png

虽然Vite确实特别好用,很多东西都帮我们做好处理了,都可以开箱即用不需要配置什么东西

但是这就让我产生了一个疑惑,整个Vue项目的工程化流程是怎样的?为什么.vue文件能够直接被解析运行?为什么能够在ts中导入.vue文件?种种疑惑让我萌生出了想要从零搭建一个Vue项目开发环境的想法

为了更好地了解工程化的流程,这里我打算使用webpack来从零搭建Vue3 + TypeScript开发环境

因为我认为webpack相比于Vite,更加地“底层化”,webpack只能理解js资源,对于非js模块资源则需要我们配置loader去额外处理

这种“底层化”的特性能够让我们更好地理解Vue项目的运行流程,哪个功能需要做哪些相关配置等等,细化对项目的掌控粒度,而不是仅仅满足于开箱即用

当然,正如Vue CLI官方文档所言,平时项目开发中还是建议直接使用Vite,因为Vite相比于webpack确实香太多了哈哈哈

开发环境初始流程搭建

Webpack

首先最重要的就是我们的构建工具 -- Webpack啦,安装如下依赖:

pnpm i webpack webpack-cli -D
  • webpackwebpack运行时的库
  • webpack-cli则允许我们在终端中使用webpack,后续的npm script中的开发环境下的和打包脚本都是需要用到该库提供的命令行工具的

然后我们在项目根目录下新建一个webpack.config.js文件,在这里对webpack进行配置

tips: 可以通过jsdoc的方式为配置对象添加类型声明,以便vscode为我们提供良好的代码提示

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development'
}

module.exports = config

这里我们先配置这么多先,其他的我们稍后再配置

TypeScript

接下来是配置TypeScript,安装如下依赖:

pnpm i typescript ts-loader -D`

ts-loader用于让webpack.ts文件识别为模块从而被加载进来,这也正是webpackloader的作用,将非js模块资源加载成js资源从而能够被执行

然后我们在项目根目录下创建一个tsconfig.json写入如下配置:

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "lib": ["DOM", "ESNext"],
    "strict": true,
    "jsx": "preserve",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "strictNullChecks": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules"]
}

我们只关心src/目录下的代码的类型检查,对于其他的代码,比如根目录下的webpack.config.js,不需要ts去检查,因此这里我们添加include配置项

由于webpack并不认识.ts的文件,因此我们需要ts-loaderts转成js,所以需要配置一下相关loader

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  module: {
    rules: [
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      },
    ],
  }
}

Vue

对于vue的支持,主要体现在对其单文件组件SFC的支持,这就需要用到vue-loader,将.vue文件加载成webpack可识别的js模块资源,本质上就是:

  • <template>部分转成h函数生成的vnode
  • <script>部分转成js代码
  • <style>部分转成css -- css最终也会被loader转成js代码

将这些转换处理好后,对vue的基本支持就算实现了,明确了目标之后就让我们来先试试吧

首先安装如下依赖

pnpm i vue
pnpm i vue-loader -D

然后修改webpack.config.js,添加如下配置

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ],
}

现在webpack就能识别.vue文件了,这些都是基础流程的搭建,还不能很好地满足我们的日常开发体验,接下来我们再来逐步朝着提高开发体验的目标前进!

让vscode识别.vue文件

首先我们来搭建一个简单的vue项目基础结构

── src
│   ├── App.vue
│   ├── components
│   │   └── Hello.vue
│   ├── main.ts

main.ts作为项目的入口,因此我们需要配置到webpack.config.js

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  entry: './src/main.ts',
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ],
}

注意:vue-loader不仅仅只是配置完loader就行了,还需要将其配置为Plugin

然后看看main.ts中的内容

image.png

咦?vscode报错找不到模块“./App.vue”或其相应的类型声明。ts(2307),这是为什么呢?

因为TypeScript并不识别.vue类型的文件,它只识别.ts文件,为了让ts支持对vue文件的类型检查,我们需要为.vue模块声明一下类型

vue官方推荐的做法是在项目中添加一个shims-vue.d.ts,通过declare为外部模块声明类型,从而有一个良好的ide体验

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

image.png

可以看到现在就不会对.vue文件报错了,并且能够完美地获取到其类型提示

配置路径别名

在以前使用Vue CLI创建的项目中,我们经常会使用到@这个路径别名,其指向的是项目根目录下的src目录,那么这个功能要怎么实现呢?

这得从两个角度去思考:

  1. webpack打包运行时对路径别名的处理
  2. TypeScript开发环境下对路径别名的识别

前者涉及到打包时能否正确处理路径别名,而后者则涉及到ide中能否对路径别名有一个良好的开发体验

首先来看看webpack这边要如何操作,主要是通过resolve.alias配置项去实现

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  entry: './src/main.ts',
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      }
    ],
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

然后是TypeScript这边对路径别名的支持,主要是通过compilerOptions.baseUrl以及compilerOptions.paths

{
  "compilerOptions": {
    ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

这样一来无论是在编写代码时还是webpack打包运行时,都能够肆无忌惮地使用类型别名啦!

开发环境服务器

为了能够有一个良好的开发体验,我们需要在代码更新时及时看到更新后的效果,这时候就需要webpack-dev-server的帮助了

pnpm i webpack-dev-server -D

然后配置devServer,并且在运行时使用webpack serve命令即可开启一个开发时服务器

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  entry: './src/main.ts',
  devServer: {
    hot: true,
    open: true,
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      }
    ],
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

hot: true表示开启HMR热更新,open则表示运行webpack serve命令后自动启动浏览器打开页面

配置入口页面

目前的配置下,我们打包后的结果只会生成js文件,要想查看效果还得手动创建一个html文件,并且手动引入打包后的js文件,每次打包后都要重复这样的过程的话十分不方便

于是我们就需要用到html-webpack-plugin插件了,这个属于webpackPlugin,用于完成一些webpack能力之外的事情的

pnpm i html-webpack-plugin -D

首先我们在项目目录下创建public/index.html作为项目入口的html页面模板

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

然后修改webpack.config.js配置文件如下:

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  entry: './src/main.ts',
  devServer: {
    hot: true,
    open: true,
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      }
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ]
}

html-webpack-plugin在每次打包时都去读取public/index.html,将其添加到打包结果中,这就是该插件的主要功能

<style>支持

目前我们的vue文件能够被加载了,但是对于SFC中的<style>部分,仍然无法正常处理,这是因为还没有加上对css的支持

我们安装如下依赖:

pnpm i css-loader style-loader sass sass-loader -D

并配置webpack.config.js如下

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  mode: 'development',
  entry: './src/main.ts',
  devtool: false,
  devServer: {
    hot: true,
    open: true,
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      // css
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // sass/scss
      {
        test: /\.(scss|sass)$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
      // typescript
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
}

对于css文件,这样配置可以理解为style-loader(css-loader(css模块)),因此配置的顺序要从后往前配置,css-loadercss文件加载成js代码的字符串,而style-loader则将样式插入到页面中

至于sass,则视工作需要和个人习惯而定,你也可以换成less或者stylus

至此,我们就完成了从零搭建的一个Vue3 + TypeScript的开发环境啦!