Vue的编译方式变啦
采用vite作为Vue的脚手架开发构建工具
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题
安装全局脚手架依赖 脚手架gitHub
cnpm install -g create-vite-app
创建脚手架项目
这里不能使用cnpm
npm create vite-app <project-name>
集成Ts
cnpm i --dev typescript
项目根目录创建配置文件:tsconfig.json
:
{
"compilerOptions": {
/* 指定编译之后的版本目录【指定ECMAScript目标版本】
"ES3","ES5","ES6","ES2015","ES2016",
"ES2017","ES2018","ES2019","ES2020","ESNext"
*/
"target": "esnext",
/* 指定要使用的模板标准
"CommonJS","AMD","System","UMD","ES6",
"ES2015","ES2020","ESNext","None"
*/
"module": "esnext",
/* 启用所有严格类型检查选项。需要TypeScript版本2.3或更高版本
boolean
*/
"strict": true,
/* jsx代码用于的开发环境
'preserve', 'react', or 'react-native'
*/
"jsx": "preserve",
/* 用来指定是否在编译的时候生成相的d.ts声明文件,
如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件,
但是declaration和allowJs不能同时设为true */
"declaration": true,
/* 用来指定编译时是否生成.map文件 */
"declarationMap": false,
/* 用来指定编译时是否生成.map文件 */
"sourceMap": false,
/* 指定是否引入tslib里的复制工具函数,默认为false */
"importHelpers": true,
/* 此处设置为node,才能解析import xx from 'xx' */
"moduleResolution": "node",
/* 启用对ES7装饰器的实验性支持 */
"experimentalDecorators": true,
"esModuleInterop": true,
/* 不报告执行不到的代码错误 */
"allowSyntheticDefaultImports": true,
// 必须标志Null 类型,才可以赋值为null
/* 当设为true时,null和undefined值不能赋值给非这两种类型的值,
别的类型的值也不能赋给他们,除了any类型,
还有个例外就是undefined可以赋值给void类型 */
"strictNullChecks": true,
/*解析 json的扩展 */
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
/* 指定要包含在编译中的库文件 */
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": [
"src/**/*",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": ["node_modules", "dist"]
}
集成eslint
cnpm i -D eslint eslint-plugin-vue
项目根目录创建配置文件.eslintrc.js
:
module.exports = {
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser", // 指定ESLint解析 Specifies the ESLint parser
ecmaVersion: 2020, // 允许解析现代ECMAScript特性 Allows for the parsing of modern ECMAScript features
sourceType: "module", // 允许使用imports Allows for the use of imports
ecmaFeatures: {
tsx: true, //允许解析tsx Allows for the parsing of JSX
jsx: true
}
},
// settings: {
// tsx: {
// version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
// }
// },
extends: [
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
"plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
rules: {
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
"comma-dangle": "off", // 修正 eslint-plugin-vue 带来的问题,
// allow async-await
"generator-star-spacing": "off",
"no-undef": "off",
camelcase: "off"
}
};
集成pritter
cnpm i -D prettier eslint-config-prettier eslint-plugin-prettier
项目根目录创建配置文件:.prettierrc.js
:
module.exports = {
/**
* @desc 用tab代替空格进行缩进
* @默认 false
* @params
* - false:使用tab
* - true:使用空格【tabWidth无效】
*/
useTabs: false,
/**
* @desc 指定每个tab缩进级别的空格数
* @默认 2
* @params number [object,改了该文件会立马格式变掉]
*/
tabWidth: 2,
/**
* @desc 指定每行最多多少个字符才换行
* @默认 80
* @params number
*/
printWidth: 100,
/**
* @desc 末尾自动使用分号
* @default true
* @params
* - true:自动加入`;`分号
* - false:自动删除`;`分号
*/
semi: true,
/**
* @desc 多行时【数组,对象等被迫换行的时候】,
* 尽可能在后面打印逗号。(提示:单行数组的末尾永远不会有逗号)
* @default es5
* @params
* - es5:在ES5中有效的尾部逗号(对象,数组等)
* - none:没有尾随逗号
* - all:尽可能在后面加上逗号(包括函数参数)
*/
trailingComma: 'all',
/**
* @desc 使用单引号,来替代双引号
* @default false
* @notes
* - JSX文件忽略这个选项--可以看 https://prettier.io/docs/en/options.html#jsx-quotes
* - 如果引号的数量超过另一个引号,则使用较少的引号将格式化字符串
* ==>示例:"I'm double quoted" 导致 "I'm double quoted"
* ==>"This \"example\" is single quoted" 导致 'This "example" is single quoted'
* @params
* - true:使用单引号代替双引号
* - false:使用双引号
*/
singleQuote: true,
/**
* @desc 当对象中的属性被引用改变
* @default as-needed
* @param
* - as-needed:只在需要的时候在对象属性周围添加引号【加不加引号自己决定】
* - consistent:对象属性都加入引号
* - preserve:尊重对象属性中引号的输入用法
*/
quoteProps: 'consistent',
/**
* @desc 在JSX中使用单引号而不是双引号
* @default false
* @params
* - false:使用双引号
* - true:使用单引号
*/
jsxSingleQuote: true,
/**
* @desc 在对象文字中的括号之间打印空格
* @default true
* @params
* - false:{foo: bar}
* - true:{ foo: bar }
*/
bracketSpacing: true,
/**
* @desc JSX标签闭合位置'>'
* @default false
* @params
* - false:
* <button
className="prettier-class"
id="prettier-id"
onClick={this.handleClick}
>
Click Here
</button>
* - true:
* <button
className="prettier-class"
id="prettier-id"
onClick={this.handleClick}>
Click Here
</button>
*/
jsxBracketSameLine: true,
/**
* @desc 在箭头函数参数周围包含圆括号
* @default always
* @params
* - always:(x)=>{}
* - avoid: x => x
*/
arrowParens: 'avoid',
/**
* @desc 默认情况下,Prettier将按原样包装markdown文本,
* 因为某些服务使用对换行符敏感的渲染器,
* 例如 GitHub注释和BitBucket。
* 在某些情况下,您可能希望改用编辑器/查看器软包装,因此此选项允许您选择“从不”。
* @default always
* @params
* - always:如果超出printWidth限制的字符,那我们就包装prose【美化代码】
* - never: 不使用包装prose【美化代码】
* - preserve: 原样包装prose【美化代码】 v1.9.0中首次可用
*/
proseWrap: 'preserve',
/**
* @desc 为HTML文件指定全局空格敏感性,详细可以看
* https://prettier.io/blog/2018/11/07/1.15.0.html#whitespace-sensitive-formatting
* @params
* - css: 尊重CSS display属性的默认值
* - strict:空格被认为是敏感的
* - ignore:空格被认为是不敏感的
*/
htmlWhitespaceSensitivity: 'ignore',
/**
* @desc 是否缩进Vue文件中<script>和<style>标记内的代码。
* 有些人(例如Vue的创建者)不缩进以保存缩进级别,但这可能会破坏编辑器中的代码折叠。
*/
vueIndentScriptAndStyle: true,
/**
* @desc 由于历史原因,文本文件中有两种常见的行结束形式。
* 即\n(或LF表示换行)和\r\n(或CRLF表示回车+换行)。
* 前者在Linux和macOS上很常见,而后者在Windows上很普遍。
* @params
* - lf:仅限换行(\n)
* - crlf:回车+换行符(\r\n)
* - cr:只用回车符(\r)
* - auto:维护现有的行结束符
*/
endOfLine: 'auto',
};
优化TS类型推断
在src目录下
-
创建
source.d.ts
【声明静态资源文件】declare module '*.json'; declare module '*.png'; declare module '*.gif'; declare module '*.jpg'; declare module '*.jpeg'; declare module '*.svg'; declare module '*.css'; declare module '*.less'; declare module '*.scss'; declare module '*.sass';
-
创建
shim.d.ts
declare module '*.vue' { import Vue from 'vue'; export default Vue; } declare module '*.tsx' { import Vue from 'vue'; export default Vue; }
安装vue-router
cnpm i -D vue-router@4.0.0-beta.2
-
在src目录下,
新建router文件夹
index.ts
import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'; const routes: RouteRecordRaw[] = [ ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router;
安装vuex
cnpm i -D vuex@4.0.0-beta.4
-
在src目录下,新建store文件夹
index.ts
import { createStore } from 'vuex'; export default createStore({ state:{}, mutations: {}, actions: {}, modules: {}, });
修改main.js==>main.ts
import { createApp } from 'vue';
import App from './App';
import router from './router';
import store from './store';
createApp(App).use(router).use(store).mount('#app');
修改App.vue==>App.tsx
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld';
import img from './assets/logo.png';
export default defineComponent({
name: 'App',
setup() {
return () => (
<div id='app'>
<img alt='Vue logo' src={img} />
<HelloWorld msg='Vue vite' />
</div>
);
},
});
修改HelloWorld.vue==>HelloWorld.tsx
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String,
},
setup(props) {
const count = ref<number>(0);
return () => (
<div class="main">
<h1> {props.msg}</h1>
<button>{count.value}</button>
</div>
);
},
});
运行
npm run dev
新增页面路由
view/home/index.vue
<template>
<div class="main">
<h1>Home</h1>
<h2>{{ title }}</h2>
</div>
</template>
<script lang="ts">
import { ref } from 'vue';
export default {
name: 'home',
setup() {
const title = ref<string>('我是标题哦');
return { title };
},
};
</script>
view/about/index.tsx
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'about',
setup() {
const title = ref<string>('我也是标题哦');
return () => (
<div class='main'>
<h1>About</h1>
<h2>{title.value}</h2>
</div>
);
},
});
路由修改
import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/home',
name: 'home',
component: () => import('../views/home/index.vue'),
},
{
path: '/about',
name: 'about',
component: () => import('../views/about/index.tsx'),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
主页面App.tsx修改
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld';
import img from './assets/logo.png';
import { RouterView, RouterLink } from 'vue-router';
export default defineComponent({
name: 'App',
setup() {
return () => (
<div id='app'>
{/* 直接使用<h2>Home</h2>会warning */}
<RouterLink to='/home'>{() => <h2>Home</h2>}</RouterLink>
<RouterLink to='/about'>{() => <h2>About</h2>}</RouterLink>
<img alt='Vue logo' src={img} />
<HelloWorld msg='Vue vite' />
<RouterView></RouterView>
</div>
);
},
});
使用store
import { createStore } from 'vuex';
export default createStore({
state: {
title: 'store标题',
},
mutations: {},
actions: {},
modules: {},
});
App.tsx使用
import { useStore } from 'vuex';
setup() {
const { state } = useStore();
return () => (
<div id='app'>
<h3>{state.title}</h3>
</div>
);
}