基于Vue Cli4搭建Vue3 TSX移动端项目(三)
上一篇基本上我们把webpack相关的一些配置都已经讲完了,本篇主要讲述打包发布时的一些配置。因为我们目前的生产环境使用的k8s,会用到docker去发布前端的代码,所以这里我用到了express作为项目的启动服务。本篇主要讲述3个点:资源上传CDN、express项目启动服务、Dockerfile
静态资源打包上传
我们知道在前端的性能优化中,使用CDN是一个比较常见的优化点。一般CDN会设置多个节点服务器,分布在不同区域中,便于用户进行数据传递和访问。当某一个节点出现问题时,通过其他节点仍然可以完成数据传输工作,可以提高用户访问网站的响应速度。且现在的云提供商基本上都采用“分布式存储”,将中心平台的内容分发到各地的边缘服务器,使用户能够就近获取所需内容,降低网络用时,提高用户访问响应速度和命中率。利用了索引、缓存等技术。
那么这里我们在项目打包时,就可以将生成的js文件,css文件等上传到云提供商的对象存储中再开启CDN,以此来利用CDN的这些特性来加快我们的前端页面加载。
安装ali-oss依赖
这里我使用了阿里云的OSS,只做一个示例,如果是非阿里云的可以根据自己的云提供商去看下node相关的SDK。具体思路就是在docker build的时候先执行一下上传脚本,然后通过配置vue.config.js的publicPath路径即可。
npm i -D ali-oss
npm i -D ali-oss
npm WARN @vue/eslint-config-typescript@7.0.0 requires a peer of @typescript-eslint/eslint-plugin@^4.4.0 but none is installed. You must install peer dependencies yourself.
npm WARN @vue/eslint-config-typescript@7.0.0 requires a peer of @typescript-eslint/parser@^4.4.0 but none is installed. You must install peer dependencies yourself.
npm WARN postcss-modules@4.0.0 requires a peer of postcss@^8.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN icss-utils@5.1.0 requires a peer of postcss@^8.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN postcss-modules-extract-imports@3.0.0 requires a peer of postcss@^8.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN postcss-modules-local-by-default@4.0.0 requires a peer of postcss@^8.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN postcss-modules-scope@3.0.0 requires a peer of postcss@^8.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN postcss-modules-values@4.0.0 requires a peer of postcss@^8.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN tslint-eslint-rules@5.4.0 requires a peer of typescript@^2.2.0 || ^3.0.0 but none is installed. You must install peer dependencies yourself.
+ ali-oss@6.15.0
added 72 packages from 123 contributors in 9.619s
编写上传文件
这里我在scripts/upload.js中新建静态资源上传的js文件,具体代码如下
const OSS = require('ali-oss')
const path = require('path')
const fs = require('fs')
const config = require('../config')
const cfg = config().buildtime.cdn
const DistPath = path.join(__dirname, '../dist');
// 这里因为这个代码只会在docker中执行,不对外暴露
// 所以我这里图省事,直接使用accessKeyId,accessKeySecret直连OSS
// 如果对于安全性要求比较高的话,可以通过接口请求STS临时票据
const OSSClient = new OSS({
region: cfg.region,
accessKeyId: cfg.accessKeyId,
accessKeySecret: cfg.accessKeySecret,
bucket: cfg.bucket
})
/**
* 递归读取所有文件
*/
function readFilePath(path, prefix, files = []) {
const fileList = fs.readdirSync(path);
for (let fileName of fileList) {
const filePath = `${path}/${fileName}`;
const stat = fs.lstatSync(filePath);
if (stat.isFile()) {
files.push({
filePath,
fileName: `${prefix ? prefix + '/' : ''}${fileName}`
})
} else if (stat.isDirectory()) {
readFilePath(filePath, fileName, files)
}
}
return files;
}
/**
* 上传静态资源到OSS
*/
function uploadFile2Oss() {
const files = readFilePath(DistPath);
for (let item of files) {
const file = fs.readFileSync(item.filePath);
OSSClient.put(cfg.path + item.fileName, file).then(res => {
if (res.res.status === 200) {
console.log('阿里云静态资源上传成功:', item.filePath)
} else {
console.log('阿里云静态资源上传失败:', item.filePath)
}
}, () => {
console.log('阿里云静态资源上传失败:', item.filePath)
});
}
}
uploadFile2Oss();
上传验证
我们package.json中添加"cdn_upload": "node ./scripts/upload.js"这个scripts,在终端中执行npm run cdn_upload后即可看到
✗ node ./scripts/upload.js
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/about.bc0d4022.js.map
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/favicon.ico
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/img/logo.82b9c7a5.png
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/about.bc0d4022.js
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/css/app.48b12f04.css
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/index.html
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/app.bf5cd594.js.map
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/app.bf5cd594.js
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/css/chunk-vendors.bc00f759.css
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/chunk-vendors.e9b1228d.js
阿里云静态资源上传成功: /Users/chenweibin/Desktop/vue-plus-example/dist/js/chunk-vendors.e9b1228d.js.map
再去阿里云OSS中查看可以看到静态资源已经完成了上传
配置Vue.config.js
在vue.config.js中添加publicPath和indexPath配置,这样我们就只需要在docker打包时,将文件上传到CDN即可。
// 获取环境配置
const cfg = require("./config/index")();
const Pxtovw = require("postcss-px-to-viewport");
const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");
const StylelintWebpackPlugin = require("stylelint-webpack-plugin");
const isDev = process.env.NODE_ENV === 'development';
module.exports = {
publicPath: isDev ? '/' : cfg.buildtime.cdn.cdn_path,
indexPath: 'index.html',
devServer: {
host: cfg.buildtime.origin_server.ip,
port: cfg.buildtime.origin_server.port,
},
chainWebpack: (config) => {
// HTML模板注入配置
config.plugin("html").tap((args) => {
// 嵌入环境配置script
const configScript = `<!--configArea--><script>window.CUSTOMCONFIG = ${JSON.stringify(
config.runtime
)}</script><!--endOfConfigArea-->`;
args[0].config = configScript;
// 非本地开发环境及生产环境时,注入eruda
if (!["local", "production"].includes(cfg.runtime.env)) {
const erudaCDN = "//cdn.bootcdn.net/ajax/libs/eruda/2.4.1/eruda.min.js";
const erudaDomCDN = "//cdn.jsdelivr.net/npm/eruda-dom@2.0.0";
const erudaScript = `<!--erudaArea-->
<script src="${erudaCDN}"></script>
<script src="${erudaDomCDN}"></script>
<script>
eruda.init({
tool: ['console', 'network', 'elements', 'resources', 'snippets', 'sources'],
});
eruda.add(erudaDom);
</script>
<!--endOfRrudaArea-->`;
args[0].eruda = erudaScript;
}
return args;
});
},
configureWebpack: {
plugins: [
// 配置HardSource加快二次构建
new HardSourceWebpackPlugin(),
// 配置styleint
new StylelintWebpackPlugin({
files: ['**/*.{vue,htm,html,css,sss,less,scss}'],
fix: true, // 开启自动修复
cache: false, // 开启缓存
emitErrors: true,
failOnError: false,
}),
],
},
css: {
loaderOptions: {
// 全局引入common.scss
sass: {
prependData: '@import "~@/assets/styles/common.scss";',
},
// 配置px2vw
postcss: {
plugins: [
new Pxtovw({
unitToConvert: "px", // 需要转换的单位,默认为"px";
viewportWidth: 750, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的小数位数
propList: ["*"], // 要进行转换的属性列表,*表示匹配所有,!表示不转换
viewportUnit: "vw", // 转换后的视口单位
fontViewportUnit: "vw", // 转换后字体使用的视口单位
selectorBlackList: [], // 不进行转换的css选择器,继续使用原有单位
minPixelValue: 1, // 设置最小的转换数值
mediaQuery: false, // 设置媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // 忽略某些文件夹下的文件
}),
],
},
},
},
};
这里有个点注意一下,Vue Router在初始化时会以
BASE_URL作为前缀,这里记得在src/router/index.ts中改写为createWebHistory()
Express启动
上面提到我们是通过Docker来部署前端项目,所以我们需要express来作为启动服务。这里我们需要利用express来实现:
- 前端路由代理
- 动态配置校验文件
前端路由代理
这里我们开启了一个express服务,然后将所有的路由全部指向dist文件夹,将路由交给前端router去处理
const express = require("express");
const path = require("path");
const ms = require("ms");
const app = express();
// 获取环境配置
const config = require("../config/index.js");
const cfg = config();
const IP = cfg.buildtime.origin_server.ip;
const PORT = cfg.buildtime.origin_server.port;
// 开启静态文件代理
app.use(
express.static(path.join(__dirname, "../dist/"), {
etag: true,
lastModified: true,
maxAge: ms("10 days"),
setHeaders: (res, path) => {
if (path.endsWith(".html")) {
res.set("Cache-Control", "no-cache");
}
}
})
);
// 页面刷新时,将路由交给静态文件
app.use((req, res) => {
res.set("Cache-Control", "no-cache");
res.sendFile(path.join(__dirname, "../dist/"));
});
// 开启监听
app.listen(PORT, function () {
console.log(`The app server is working at: http://${IP}:${PORT}`);
});
动态配置校验文件
因为这个是移动端项目,所以避免不了跟微信、支付宝之类的第三方平台打交道。例如我们在设置微信公众号安全域名时,一般会提供给我们一个txt文件让我们放在项目根目录下。但是我们使用k8s的话,一般都需要我们去添加文件然后重新构建部署项目。所幸我们这里用到express,那么我们可以通过express来实现将txt文件上传到CDN然后自动读取的功能。
首先我们在之前提到的环境配置buildtime.cdn中添加"verifyCdnPath": "https://frontend.oss-cn-shenzhen.aliyuncs.com/vue-plus/verifyCdnPath"配置项,作为校验文件的存放地址。
接着我们创建一个get请求,匹配/*.txt(这里我默认认为所有的配置文件都是TXT格式)。然后将所有匹配到该路由的请求去访问CDN,成功请求到文件则直接返回,请求失败则跳转到前端路由。
const express = require("express");
const https = require('https');
const path = require("path");
const ms = require("ms");
const app = express();
// 获取环境配置
const config = require("../config/index.js");
const cfg = config();
const IP = cfg.buildtime.origin_server.ip;
const PORT = cfg.buildtime.origin_server.port;
// 校验文件配置
app.get("/*.txt", (req, res) => {
https.get(`${cfg.buildtime.cdn.verifyCdnPath}${req.url}`, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
res.send(data);
});
}).on("error", (err) => {
req.next()
});
})
// 开启静态文件代理
app.use(
express.static(path.join(__dirname, "../dist/"), {
etag: true,
lastModified: true,
maxAge: ms("10 days"),
setHeaders: (res, path) => {
if (path.endsWith(".html")) {
res.set("Cache-Control", "no-cache");
}
}
})
);
// 页面刷新时,将路由交给静态文件
app.use((req, res) => {
res.set("Cache-Control", "no-cache");
res.sendFile(path.join(__dirname, "../dist/"));
});
// 开启监听
app.listen(PORT, function () {
console.log(`The app server is working at: http://${IP}:${PORT}`);
});
配置package.json
我们在package.json的scripts新增"start": "node ./bin/server.js"。然后我们手动模拟触发下打包、上传、启动观察下服务是否正常启动。
我们再测试下校验文件,这里我上传了个text.txt文件作为测试,可以现在校验文件内容已成功返回。
Docker构建
dockerfile
在项目根目录下创建dockerfile文件。然后我们进行下列Dockerfile构建:
- 选择基础镜像为
node 13 - 指定工作目录
- 复制所需的配置文件安装依赖
- 复制代码
- 声明front_end环境变量
- build打包
- 静态资源上传CDN
- 暴露端口号
- 启动服务
# 基础镜像
FROM node:13
# Workdir is unprivileged user home
WORKDIR /usr/src/app
# 安装依赖
COPY package.json /usr/src/app
COPY package-lock.json /usr/src/app
COPY .npmrc /usr/src/app
RUN npm install
# 复制代码
COPY . /usr/src/app/
# 声明环境变量
ARG front_env=''
# build正式环境
RUN npm run build
# 静态资源上传到cdn
RUN npm run cdn_upload
# 暴露内部端口号
EXPOSE 5000
# 起服务
ENTRYPOINT ["npm", "run"]
CMD ["start"]
docker build
dockerfile文件写好后,一般是通过jenkins进行docker构建,不过我们现在仍然可以在本地进行测试,在项目根目录下执行:docker build . -t vue-plus --build-arg front_env=test
指令解释:
docker build .:指定build的目录为当前目录,这里不能少了这个点-t vue-plus:镜像名为vue-plus--build-arg front_env=test:自定义环境变量参数,这里我指定了front_env为test环境
执行结果:
✗ docker build . -t vue-plus --build-arg front_env=test
[+] Building 2.6s (14/14) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:13 0.0s
=> [internal] load build context 2.4s
=> => transferring context: 3.15MB 2.3s
=> [1/9] FROM docker.io/library/node:13 0.0s
=> CACHED [2/9] WORKDIR /usr/src/app 0.0s
=> CACHED [3/9] COPY package.json /usr/src/app 0.0s
=> CACHED [4/9] COPY package-lock.json /usr/src/app 0.0s
=> CACHED [5/9] COPY .npmrc /usr/src/app 0.0s
=> CACHED [6/9] RUN npm install 0.0s
=> CACHED [7/9] COPY . /usr/src/app/ 0.0s
=> CACHED [8/9] RUN npm run build 0.0s
=> CACHED [9/9] RUN npm run cdn_upload 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:5ca9f2f4143692bff98ddd73f00da7a5eecea2d15122cc672d4a313e6d3d6b0d 0.0s
=> => naming to docker.io/library/vue-plus
构建完成后,执行docker images查看构建出来的docker镜像
docker run
上面已经看到了我们构建后的docker镜像,接下来我们将docker在本地部署起来。执行指令
docker run --name vue-plus-example -d --restart=always -p 5000:5000 5ca9f2f41436
指令解释:
--name vue-plus-example:设置容器名为vue-plus-example-p 5000:5000:将本地5000端口映射到docker容器中的5000端口5ca9f2f41436:是刚刚我们生成的镜像的IMAGE ID
执行结果:
~ docker run --name vue-plus-example -d --restart=always -p 5000:5000 5ca9f2f41436
e32cbf972f092369cf83f4c0b1ff087b653053bb260d70bc3b842f1688072db7
部署成功后,我们通过 docker ps查看下当前部署是否成功。这里主要关注STATUS,如果一直在重启的话,那么就是镜像里面的服务启动有问题。
执行结果:
本地访问
我们成功部署后,本地的5000端口就会映射到镜像的5000端口。这时候我们可以通过浏览器访问http://127.0.0.1:5000来访问这个服务。
OK,至此基本上完成了这个项目所有配置及部署。下一章主要讲述下在Vue3中封装通用的Hooks、使用TSX写法进行开发及vue中常见语法的TSX写法