vue3+vite2.0+tsx/jsx项目搭建

3,472

动机

从去年三月份开始工作接触前端,入手vue与angular。到去年十月份开始研究学习uniapp框架并投入生产使用,用了将近一年多的uniapp来写微信小程序以及H5。鑫管家小程序正式上线之后,正值vite2.0发布,加上邱老师说他在研究vue3+vite的前端框架。

自己心痒痒手也痒痒,于是自己一步步查找资料、不断尝试踩坑填坑,算是搭建了一个算是比(mei)较(you)完(bao)整(cuo)的基础项目包。想着不断填坑实在是不容易,所以想着写篇文章记录一下过程中遇到的问题,如果有幸能帮到你,那......那你是不是得来个一键三连?

先贴地址:gitlab.com

从vite1.0开始

Vite说到底就是一个web开发构建工具。vite是法语中"fast"的意思/vit/。开发过程中通过本机ES模块导入为coding服务,并通过rollup用于生产。

  • 闪电般快速的冷服务器启动
  • 即时热模块更换(HMR)
  • 真正的按需编译

简而言之,就是。开始搭建的时候因为vite2.0刚刚发布,想着还是从1.0开始入手。如果想直接搭建2.0的话,我后面会介绍1.0与2.0的区别以及如何改造。我的建议是一定要看官方文档,一定要看!!

新建项目

我的node版本为14.15.0 ,讲道理的话node版本在12.0.0以上应该都没什么问题,如果它讲道理的话。

npm create vite-app project-name

这里就要说一说我遇到的第一个坑了,因为我是直接在vscode中导入一个空文件夹后执行命令创建项目的。于是在vscode中会多一层目录,vscode一直报错但是又不影响运行,以至于项目依赖都集成好了之后,这么个错误我又折腾了两三天,要了邱老师的demo做对比。

福尔摩斯说过:When you have eliminated the impossibles , whatever remains , however improbable , must be the truth!

大胆假设,删除次级目录,东西全部移到根目录下,问题解决。

福尔摩斯说得好啊!但是因为当时没有记录错误,现在我试着再次创建一个次级文件夹把项目全部丢进去居然也无法复现了。编程的魅力大概就在此处。当时推断可能是vscode的eslint插件找不到根目录下的.eslintrc.js文件导致。这个后续我确认问题所在后再记录清楚。接着往下走吧。

集成eslint、pritter、typescript

  • eslint 因为邱老师给的demo的问题就是eslint集成出了问题。所以在这上面也算是踩了不少坑。
npm add --dev eslint eslint-plugin-vue

我之前想着集成的eslint能够在运行和打包时能够扫描代码报错,集成了这么个插件。

于是运行报错: 这个报错意味着什么??

没错,意味着套娃的开始!在套了八九层娃之后,运行仍然报错并且无娃可套了。

拜拜了您嘞。扬了!

根目录下创建.eslintrc.js文件

module.exports = {
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },

  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier/@typescript-eslint',
    'plugin:prettier/recommended',
  ],
  rules: {
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    'vue/custom-event-name-casing': 'off',
    'no-use-before-define': 'off',
    // 'no-setting-before-define': [
    //   'error',
    //   {
    //     functions: false,
    //     classes: true,
    //   },
    // ],
    '@typescript-eslint/no-use-before-define': 'off',
    // '@typescript-eslint/no-setting-before-define': [
    //   'error',
    //   {
    //     functions: false,
    //     classes: true,
    //   },
    // ],
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^h$',
        varsIgnorePattern: '^h$',
      },
    ],
    'no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^h$',
        varsIgnorePattern: '^h$',
      },
    ],
    'space-before-function-paren': 'off',
  },
};

针对于vue3的template中可以有多个根节点,eslint报错

The template root requires exactly one element.eslintvue/no-multiple-template-root

只需要在.eslintrc.js文件中的rules中添加以下规则即可。

'vue/no-multiple-template-root': 'off'

这样勉强算是集成了eslint,但是也只是在vscode中标红代码而已,在运行与打包时并没有扫描所有文件并且阻断项目运行。如果您有集成相关插件能够实现该效果,求求您了告诉我吧!

  • pritter 这玩意没啥好说的
npm add --dev prettier eslint-config-prettier eslint-plugin-prettier

根目录下创建文件prettier.config.js

module.exports = {
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  semi: true,
  vueIndentScriptAndStyle: true,
  singleQuote: true,
  quoteProps: 'as-needed',
  bracketSpacing: true,
  trailingComma: 'es5',
  jsxBracketSameLine: false,
  jsxSingleQuote: false,
  arrowParens: 'always',
  insertPragma: false,
  requirePragma: false,
  proseWrap: 'never',
  htmlWhitespaceSensitivity: 'strict',
  endOfLine: 'lf',
  rangeStart: 0,
  overrides: [
    {
      files: '*.md',
      options: {
        tabWidth: 2,
      },
    },
  ],
};

按照自己的编码习惯设置就好

  • typescript
npm add --dev typescript

项目根目录创建配置文件:tsconfig.json

{
    "compilerOptions": {
        "target": "ESnext",
        "module": "ESnext",
        "jsx": "preserve",
        "strict": true,
        "importHelpers": true,
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "baseUrl": ".",
        "paths": {
            "/@/*": [
                "src/*"
            ]
        }
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
    ],
    "exclude": [
        "node_modules",
        "dist",
        "**/*.js"
    ]
}

说实话这段配置我有点与别的教程不一样,主要是我在写页面时用的是.tsx文件,很多语法报错,所以修改了部分tsconfig.json中的配置项,这些区别我会在后面具体的报错点解释,请继续往下看吧。

配置文件

  • vite.config.ts 根目录下创建vite.config.ts文件
import path from 'path';
import vueJsxPlugin from 'vite-plugin-vue-jsx';
module.exports = {
    alias: {
        '/@/': path.resolve('src'),
    },
    optimizeDeps: {
    },
    hostname: '0.0.0.0',
    port: 8080,
    // 是否自动在浏览器打开
    open: true,
    // 是否开启 https
    https: false,
    // 服务端渲染
    ssr: false,
    // 在生产中服务时的基本公共路径。
    base: './',
    outDir: 'dist',
    // 反向代理
    // proxy: {
    //     '/api': {
    //     target: 'https://blog.csdn.net/weixin_45292658',
    //     changeOrigin: true,
    //     rewrite: path => path.replace(/^\/api/, '')
    //     }
    // },
    plugins: [
      vueJsxPlugin()
    ]
};

该配置文件为vite1.0版本配置文件

然后这里说一下vite-plugin-vue-jsx这个插件,起源于邱老师解决于邱老师。项目刚搭建的时候,他突然跟我说,.tsx文件里写的dom元素,双向绑定不行了,vue指令不识别了,可把他愁坏了。

果然我搭建完项目抄了个页面一看,确实不好使。遂问邱老师如何解决的。然后引入了vite-plugin-vue-jsx,在plugins中注册一下。解决!

只要大腿抱的好,没有问题解决不了!

然后下面是vite2.0的配置文件

import { defineConfig } from 'vite';
import path from 'path';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { buildConfig } from './build/config/buildConfig';
export default defineConfig({
  server: {
    port: 7874,
    proxy: {
      // '/lsbdb': 'http://10.192.195.96:8087',
    },
    hmr: {
      overlay: true,
    },
    open: true,
  },
  build: buildConfig,
  alias: {
    '/@': path.resolve(__dirname, '.', 'src'),
  },
  optimizeDeps: {
    include: [],
  },
  plugins: [vue(), vueJsx()],
});

因为vite2.0与vue解耦,所以咱得安装@vitejs/plugin-vue这玩意,引入后在plugins中引入。然后@vitejs/plugin-vue-jsx这个玩意,就是vite1.0中的vite-plugin-vue-jsx,换了个名字一跃成为官方组件。

然后先贴一下官方关于1.0与2.0改造文档地址 主要就是把本地运行相关配置丢到了server一个大object中了。最让我无语的是,别名配置alias:

vite1.0: '/@/': path.resolve('src'),
vite2.0: '/@': path.resolve(__dirname, '.', 'src'),

它居然删除了一个/,就这玩意,我从1.0升级到2.0之后,项目内引入全部报错,百度也完全找不到原因。幸好找到了官方文档,对这个更改有做精确说明。所以说一定得看官方文档,虽然是全英文也得看。

然后下面贴一下我的打包配置buildConfig.ts

const buildConfig = {
  //在生产中使用时的基本公共路径。请注意,路径应以/开头和结尾
  base: '/',
  //默认值为Vite特殊值'modules',另一个特殊的值是'esnext'-仅执行最少的跨语言转换(用于最小化兼容性),并假定支持本机动态导入。
  target: 'modules',
  //是否自动注入动态导入的polyfill。
  polyfillDynamicImport: true,
  //指定输出目录
  outDir: 'dist',
  //指定目录以将生成的动态自由嵌套在下
  assetsDir: 'assets',
  //小于此阈值的导入或引用资产将作为base64 URL内联,以避免额外的http请求。设置为0完全禁用内联。
  assetsInlineLimit: 4096,
  //启用/禁用CSS代码拆分。启用后,在异步块中导入的CSS将内联到异步块本身中,并在加载块时插入。如果禁用,则整个项目中的所有CSS都将提取到一个CSS文件中。
  cssCodeSplit: true,
  //Generate production source maps.生成生产源图。
  sourcemap: false,
  //设置为时true,构建还将生成一个manifest.json文件,其中包含未哈希静态资源文件名到其哈希版本的映射,然后服务器框架可以使用该文件来呈现正确的静态资源链接。
  manifest: false,
};
export { buildConfig };

这一块我是按照官方文档自己配置的,也就是把一些常用的默认配置拉出来了而已,然后关于打包压缩minify还未配置,这一块后续还得研究一下。

router和vuex

  • 集成vue-router
npm add --dev vue-router

src下新建router文件夹,文件夹下新建index.ts文件

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'index',
    component: () => import('/@/views/Index'),
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('/@/views/Login'),
  },
];
const router = createRouter({
  history: createWebHistory(),
  routes,
});
export default router;
  • 集成vuex
npm add --dev vuex

src下新建store文件夹,文件夹下新建index.ts文件

import { createStore } from 'vuex';

export default createStore({
  state() {
    return {
      count: 0,
    };
  },
  mutations: {
    increment(state: any) {
      state.count++;
    },
  },
  actions: {
    increment(context) {
      context.commit('increment');
    },
  },
});
  • 修改main.ts与app.tsx 修改main.js为main.ts文件
import { createApp } from 'vue';
import router from '/@/router';
import store from '/@/store';
import App from '/@/App.tsx';
import '/@/styles/index.scss';
console.log(store);
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');

修改app.vue文件为app.tsx文件

import { RouterLink, RouterView } from 'vue-router';
export default {
  render() {
    return (
        <RouterView></RouterView>
    );
  },
};

然后index.html中更改main.js为main.ts,完事!

优化TS类型推断

在src目录下,创建shims-vue.d.ts、source.d.ts、vue.d.ts文件

  • shims-vue.d.ts: (如果项目中全是通过tsx开发的,这个玩意就不太需要了)
declare module '*.vue' {
  import { defineComponent } from 'vue';
  const component: ReturnType<typeof defineComponent>;
  export default component;
}

用邱老师的话说,要是在vue3里不用tsx开发,那还不如用vue2呢。 然后在这里贴一篇文章吧:为什么我推荐使用JSX开发Vue3

  • source.d.ts: (优化编译器提示,声明静态资源文件)
declare module '*.png' {
  const src: string;
  export default src;
}
  • vue.d.ts:
import { Store } from 'vuex';
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $store: Store<any>;
  }
}

至此一个基本的项目框架就差不多搭建好了。

有个问题在于我在.tsx文件中像react中写jsx一样写render中内容时,tsconfig.json当中有个配置项叫做jsx

{
    "compilerOptions": {
        "jsx": "preserve"
    }
}

这个配置项我一开始给的值为react。 然后我的页面就会报错 Cannot find name 'React'. 这个报错真是从头到尾贯穿了我整个人生!但是因为不影响运行也没细致追究。然后越看越难受,强迫症发作。 我就怀疑是这个tsconfig中配置的这个项的问题,邱老师说你换一个试试,我给换成了preserve。问题解决。

后记

讲道理在搭建整个框架过程中,我大部分时间想的还是如何解决报错。解决问题和报错的过程中,也翻过一些源码和配置项。但是很多配置项的实际意义仍然不是很清楚,这个后续需要整明白的地方。

后续打算:

  • 引入axios或者邱老师说的fetch封装异步请求。
  • 引入postcss后置处理器。
  • 引入router异步加载机制。
  • 引入全局过滤器。
  • 封装一些全局常用组件。

后续集成了再更新,如果您觉得这篇文章有帮助的话,帮忙点个赞吧。有什么写的不好或者不正确的话,希望能帮忙指正。有什么问题也可以一起交流。

2021/2/23更新 已更新了postcss、axios、mock。

2021/3/10更新 因为vue3中移除了过滤器,写了一些全局处理函数注入$filter中供全局调用