web5deployVue3 目录
webpack5
搭建一个完整的vue3
的开发环境webpack.config.js
webpack
npm install webpack webpack-cli -D
webpack.config.js
文件用于编写webpack
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
src
目录存放源代码// src/index.js
console.log('test webpack');
package.json
中的scripts
字段 "scripts": {
"build": "webpack"
},
js
npm run build
或者只输入webpack
dist
文件夹,打包后的文件就是main.js
效果
package.json
和webpack.config.js
npm run build
or webpack
由于存在一些浏览器无法使用ES6+
的高级语法,因此需要转换为ES5
cnpm install @babel/core babel-loader @babel/preset-env -D
webpack.config.js
配置// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
如果不想将配置写在配置文件中,可以在根目录创建babel.config.js
或者babelrc.js
webpack
默认只能打包commonJS
规范的js
文件cnpm install style-loader css-loader less less-loader -D
webpack.config.js
配置// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
}
]
}
}
loader
的配置有很多能优化的地方
除js
文件的其他文件打包,webpack
都需要特定的处理器进行处理
npm install url-loader file-loader -D
webapck.config.js
配置 {
test: /\.(jpg|png|jpeg|gif|bmp)$/,
use: {
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
}
},
{
test: /\.(mp4|ogg|mp3|wav)$/,
use: {
loader: 'url-loader',
options: {
limit:1024,
fallback: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
}
}
令打包的js
文件自动插入到html
模板中
cnpm install html-webpack-plugin -D
webpack
配置const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
title: 'vue3 + TS -> web App'
})
]
配置动态网页标题时,需要将模板中的
标签里的内容换成<%= htmlWebpackPlugin.options.title %>
每次打包后,都需要手动点击dist
目录下的index.html
看效果,因此,可以设置webpack
将打包后的文件自动在浏览器打开
cnpm install webpack-dev-server -D
webpack
配置module.exports = {
//...
devServer: {
port: 3000,
hot: true,
open: true,
contentBase: '../dist'
},
//...
}
hash
,那每次打包生成的文件都会在dist
目录保留cnpm install clean-webpack-plugin -D
webpack
配置const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebPackPlugin()
]
.env
文件cross-env
以cross-env
的方式来设置环境变量:可以跨终端进行设置
cnpm install cross-env -D
package.json
配置 "scripts": {
"build": "webpack",
"webpack": "cross-env NODE_ENV=development webpack"
},
开发一个项目的最终要求就是要达到功能完备的情况下,打包体积最小,保证用户体验
html
文件- 修改`webpack`配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
// ...
+ minify: {
+ collapseWhitespace: true, // 去掉空格
+ removeComments: true // 去掉注释
+ }
}),
// ...
]
}
css
文件1. 安装依赖
cnpm install mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
2. 修改webpack
配置文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
},
// ...
]
},
plugins: [
// ...
new OptimizeCssAssetsWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].css'
})
]
}
purgecss-webpack-plugin
用于清除无用的css
js
文件1. 安装依赖
cnpm install terser-webpack-plugin -D
2. 修改webpack
配置
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin()
]
},
// ...
}
uglifyjs-webpack-plugin
不支持压缩ES6
语法的代码
cnpm install image-webpack-loader -D
webpack
文件module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(jpg|png|jpeg|gif|bmp)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
}
]
},
// ...
]
},
// ...
}
在安装image-webpack-loader
依赖时,采用cnpm
安装,npm
会报错
TypeScript
Vue3
源码全部采用TS
重写cnpm install typescript ts-loader -D
webpack
配置module.exports = {
// ...
module: {
rules: [
{
test: /\.ts$/,
use: [
'ts-loader'
]
},
// ...
]
},
// ...
}
tsconfig.json
文件tsc --init
tsc --init
报错没有此命令时,需要先在全局安装npm install -g typescript
安装成功:
.Vue
文件cnpm install vue@next -S
cnpm install vue-loader@next @vue/compiler-sfc
如果使用的是Vue2.x
,需要安装的是vue-template-complier
webpack
文件const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
use: [
'vue-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
]
}
index.js
文件中引入vue
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app')
新增App.vue
文件
//App.vue
解析vue文件
{{name}}
defineComponent
只是为了在使用vue3
时有会很好的语法提示
vue3
受到React Hooks
的启发,将以前的options API
改写成函数式API
tree-shaking
,提高代码的复用率Minxin
可以完成公共逻辑代码的抽离,但是Mixin
存在诸多缺点minxin
里的先执行等等Composition API
vue2.x
的响应式底层核心是采用Object.defineProperty
来劫持对象的每个属性的getter
和setter
,方法是defineReactive
vue2.x
核心原理// src\core\observer\index.js
if (Array.isArray(value)) {
this.observeArray(value)
} else {
this.walk(value)
}
// 处理对象
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
// 处理数组
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // 收集依赖
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 通知更新
}
})
}
array
和Object
时,进行判断,然后分别处理递归
操作,且对象每个属性都劫持,性能会变差vue2.x
总结缺点:
Vue.$set
和Vue.delete()
length
属性无法检测 解决办法:splice
Vue3
核心原理采用了proxy
作为底层响应式的核心API
// packages\reactivity\src\reactive.ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
return proxy
}
vue3
总结createReactiveObject
方法是创建响应式的核心方法proxy
直接监听整个对象,没有对数组和对象进行分开处理set
, weakset
, map
, weakmap
, reflect
等ES6
的新特性Vue-Router
npm install vue-router@4 -S
Home.vue
和Me.vue
// Home.vue
// Home.vue
首页
// Me.vue
// Me.vue
我的页面
router.js
文件import { createRouter, createWebHistory } from "vue-router";
import Home from './Home.vue';
import Me from './Me.vue';
const routerHistory = createWebHistory();
const router = createRouter({
history: routerHistory,
routes: [
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/me',
name: 'Me',
component: Me
}
]
});
export default router;
index.js
文件console.log('test webpack');
import { createApp } from 'vue';
import App from './App.vue';
import router from './router.js';
createApp(App).use(router).mount('#app')
cnpm install vuex@next -S
store.js
文件import { createStore } from "vuex";
const store = createStore({
state: {
name: 'vuex'
},
getters: {},
actions: {},
mutations: {},
modules: {}
});
export default store;
index.js
文件console.log('test webpack');
import { createApp } from 'vue';
import App from './App.vue';
import router from './router.js';
import store from "./store.js";
createApp(App).use(router).use(store).mount('#app')
App.vue
中获取vuex
中的数据<template>
<div>
<!-- ... -->
<p>获取vuex里面的数据{{count}}</p>
<!-- ... -->
</div>
</template>
<script>
import { defineComponent, computed } from 'vue';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
const store = useStore();
const count = computed(() => store.state.count)
return {
count
}
}
})
</script>
computed
从store
中获取数据,保证数据的响应式cnpm install vant@next -S
cnpm i babel-plugin-import ts-import-plugin -D
> JS
// babel.config.js
module.exports = {
plugins: [
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: true,
},
'vant',
],
]
};
TS
const tsImportPluginFactory = require('ts-import-plugin');
module.exports = {
// ...
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: () => ({
before: [
tsImportPluginFactory({
libraryName: 'vant',
libraryDirectory: 'es',
style: (name) => `${name}/style/less`,
}),
],
}),
compilerOptions: {
module: 'es2015',
},
}
},
],
exclude: /node_modules/
},
// ...
],
}
};
cnpm install lib-flexible -S
cnpm install postcss-pxtorem -D
.postcssrc.js
module.exports = {
plugins:{
// autoprefixer: {
// browsers: ['Android >= 4.0', 'iOS >= 8']
// },
'postcss-pxtorem': {
// rootValue: 37.5, // Vant 官方根字体大小是 37.5
rootValue({file}) {
return file.indexOf('vant') !== -1 ? 37.5 : 75
},
propList: ['*'],
selectorBlackList: ['.norem'] // 过滤掉.norem-开头的class,不进行rem转换
}
},
}
browsers 选项需配置在 package.json,不然打包会有警告
// package.json
{
// ...
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"Android >= 4.0",
"iOS >= 8"
]
}
// index.js
import { createApp } from 'vue';
+ import 'lib-flexible/flexible';
import App from './App.vue';
import router from './router.js';
import store from './store.js';
createApp(App).use(router).use(store).mount('#app')
// Home.vue
<template>
<div>
首页
<v-button plain hairline type="primary">细边框按钮</v-button>
<v-button plain hairline type="primary">细边框按钮</v-button>
</div>
</template>
<script>
import { defineComponent, ref } from "vue";
import { Button } from "vant";
export default defineComponent({
name: "Home",
components: {
"v-button": Button
}
});
</script>
webpack5
将各个环境配置好后,规范一下项目的目录结构
tree -I "node_modules"
├─dist
│ ├─css
│ └─js
| |-favicon.ico
| |-index.html
├─node_modules
├─public
| |-index.html
| |-favicon.ico
└─src
| ├─api
| ├─components
| ├─hooks
| ├─router
| ├─store
| ├─utils
| └─views
| |-App.vue
| |-main.ts
|-.gitigore
|-babel.config.js
|-package.json
|-shims-vue.d.ts
|-tsconfig.json
|-webpack.config.js
vue
脚手架生成的项目目录由于typescript
只能理解.ts
文件,无法理解.vue
文件,需要在根目录创建一个后缀名为.d.ts
文件
// shims-vue.d.ts
declare module '*.vue' {
import { ComponentOptions } from 'vue';
const componentOptions: ComponentOptions;
export default componentOptions;
}
cnpm install friendly-errors-webapck-plugin node-notifier -D
webpack
配置const path = require('path');
+ const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
+ const notifier = require('node-notifier');
+ const icon = path.join(__dirname, 'public/icon.jpg');
module.exports = {
// ...
plugins: [
new FriendlyErrorsWebpackPlugin({
onErrors: (severity, errors) => {
notifier.notify({
title: 'webpack 编译失败了~',
message: `${severity} ${errors[0].name}`,
subtitle: errors[0].file || '',
icon,
});
},
}),
// ...
],
};
cnpm install webpack-bundle-analyzer -D
webpack
配置+ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin(),
// ...
],
};
package.json
{
"scripts": {
// ...
"analyzer": "webpack --progress"
},
}
npm run analyzer
,系统自动启动打包报告的HTTP服务器state.json
文件,后续查看+ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true
}),
// ...
],
};
package.json
{
"scripts": {
// ...
+ "analyzers": "webpack-bundle-analyzer --port 3000 ./dist/stats.json"
},
}
此插件可以直观显示每个依赖打包所花费的时间
cnpm install speed-measure-webpack5-plugin -D
webpack
配置const SpeedMeasureWebpack5Plugin = require('speed-measure-webpack5-plugin');
const smw = new SpeedMeasureWebpack5Plugin();
module.exports = smw({
// options
})
const path = require('path');
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
},
// ...
],
},
};
babel-loader
中可以配置缓存loader
如果要缓存,需要下载cache-loader
cnpm install cache-loader -D
webpack
配置const path = require('path');
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
+ cacheDirectory: true
},
},
// ...
},
{
test: /\.css$/,
use: ['cache-loader', 'style-loader', 'css-loader'],
}
// ...
],
},
};
统一代码规范:
git
提交前校验代码检查工具
.eslintrc.js
module.exports = {
root: true, // 此项是用来告诉eslint找当前配置文件不能往父级查找
env: {
node: true, // 此项指定环境的全局变量,下面的配置指定为node环境
},
extends: ['plugin:vue/recommended', '@vue/prettier'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/no-v-html': 'off',
},
parserOptions: {
parser: 'babel-eslint',
parser: 'babel-eslint',
ecmaVersion: 7,
sourceType: 'module',
ecmaFeatures: {
// 添加ES特性支持,使之能够识别ES6语法
jsx: true,
},
},
overrides: [],
};
.eslintignore
文件# .eslintignore 不需要检查的文件
src/assets
src/icons
public
dist
node_modules
代码格式化工具
prettier.config.js
文件module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'always',
htmlWhitespaceSensitivity: 'ignore',
vueIndentScriptAndStyle: true,
endOfLine: 'lf',
}
规范化css
的书写,风格统一,减少错误
.stylelintrc.js
文件module.exports = {
extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
}
.editorconfig
文件root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
npm install -g commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
git cz
来取代git commit
yarn add husky @commitlint/config-conventional @commitlint/cli -D
commit
:负责对commit message
进行格式检验husky
:负责提供更易用的git hook
commit.config.js
echo 'module.exports = {extends: ["@commitlint/config-conventional"]};' > ./commitlint.config.js
utf-8
格式,否则报错package.json
文件中引入husky
"husky": {
"hooks": {
"commit-msg": "commitlint -e $GIT_PARAMS"
}
}
git add .
git cz
选择并输入git push -u origin branchname
自动化发布
CI/CD
自动化测试发布
let client = require('scp2');
const ora = require('ora');
const chalk = require('chalk');
const spinner = ora(chalk.green('正在发布到服务器...'))
spinner.start()
client.scp('./dist', { // 本地打包的路径
'host': 'xxx.xxx.x.xxx', // 服务器的IP地址
'post': '22', // 服务器的IP地址
'username': 'xxxx', // 用户名
'password': '*****', // 密码
'path': '/opt/stu_app_website' // 项目需要部署到服务器的位置
}, err => {
spinner.stop();
if(!err) {
console.log(chalk.green('项目发布完毕'))
} else {
console.log('err', err)
}
})
总结