基本介绍
Vevor 网站是一个跨境电商系统,按传统 MVC 领域模型划分,Model 由交易中台提供底层能力,Controller 层由 PHP 提供网站服务聚合,View 视图层由前端负责,也是本文主要介绍的重点。
基础架构图
前端架构简述
整体更像一个多页应用,前端的视图能力依赖 PHP 路由提供的视图渲染,针对在需要 SEO 的页面使用 PHP Blade 模板引擎直出 HTML 进行 SSR 渲染,在一些私有的页面如:个人中心、购物车、结算页使用 Vue 进行开发使用 CSR 渲染。
前端构建
多入口生成
Webpack entry 自带支持多个入口配置,多页应用需要考虑如何自动识别入口文件,而不是每次添加页面后手动的修改 webpack 配置,简单策略:与文件夹同名的 .js 文件视为一个入口,如:/assets/pages/home/home.js 即为一个入口。
1 2 3 4 5 6 7 8
| const glob = require("glob"); const path = require('path');
function getEntry(pages) { const entryes = glob.sync(pages) .filter(filePath => path.dirname(filePath).split("/").pop() === path.basename(filePath).split(".")[0]); return entryes; }
|
公共模块抽离
主要根据核心框架、公共模块进行配置,具体分为 4 个资源组:
- vue.js 核心框架(非所有页面通用故单独抽离成独立的文件)
- lib.js 外部依赖(包含同步和异步)
- common.js 同步 chunks
- async.js 异步 chunks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| optimization: { moduleIds: 'deterministic', runtimeChunk: 'single', splitChunks: { cacheGroups: { vue: { name: 'vue', test: /[\\/]vue[\\/]|[\\/]vue-router|element-ui[\\/]/, chunks: 'all', enforce: true, minChunks: 1, priority: 30, }, lib: { name: 'lib', test: /[\\/]node_modules[\\/]/, chunks: 'all', minChunks: 5, priority: 20, }, common: { name: 'common', chunks: 'initial', minChunks: 5, priority: 10, reuseExistingChunk: true, }, async: { chunks: 'async', minChunks: 5, priority: 1, reuseExistingChunk: true, } } } },
|
CSS 抽离
CSS 抽离较复杂需要考虑抽离行内资源和外链资源两种形式,同步的页面使用行内的形式注入减少外链请求阻塞渲染提高首屏能力。异步渲染的页面使用外链的形式注入。根据配置过滤多 entry 中需要生成内联资源的入口生成 html-webpack-plugin
配置后,再使用 html-inline-css-webpack-plugin
插件提取该入口的所有的资源为单独的文件。
1 2 3 4 5 6 7 8 9 10
| plugins: [ ...Object.values(getTemplates()).map(temp => new HtmlWebpackPlugin(temp)), new HTMLInlineCSSWebpackPlugin({ leaveCSSFile: true, replace: { target: '</head>', removeTarget: true } }), ]
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| const path = require('path'); module.exports = { // 'auth/sign_in': { // template: path.resolve(__dirname, '../../views/inlineSource/tpl.html'), // filename: path.resolve(__dirname, '../../views/inlineSource/include/users_user_login.blade.php'), // inlineSource: '.*\\.css' // }, // 'auth/register': { // template: path.resolve(__dirname, '../../views/inlineSource/tpl.html'), // filename: path.resolve(__dirname, '../../views/inlineSource/include/users_user_register.blade.php'), // inlineSource: '.*\\.css' // } };
|
资源注入
如上所述有前端视图层依赖 PHP,所以没办法使用 html-webpack-plugin
生成 html 文件自动注入资源,而是使用 webpack-manifest-plugin
插件输出原始资源和打包后资源资源映射关系,配合 PHP 提供 helper 函数帮助下手动注入到视图层。如下所示为一个基础的 layout 布局文件,其他页面可继承当前页面,再注入当前页面的资源即可。实现了 html-webpack-plugin 同样的功能而且更为灵活。
1 2 3 4 5 6 7 8
| { "/css/common.css": "/20211222/css/common-ec63635926d8.css", "/css/layouts/base.css": "/20211222/css/layouts~base-c094375bada4.css", "/js/runtime.js": "/20211222/js/runtime-82ddced9ebf7.js", "/js/lib.js": "/20211222/js/lib-d4ab0d668444.js", "/js/common.js": "/20211222/js/common-2ea54f6939d1.js", ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> {{-- 头部样式 --}} @include('layouts.component.header_style') <link rel="stylesheet" href="{{ g_mix('/css/common.css')}}"> {{-- 全局公共样式 --}} <link rel="stylesheet" href="{{ g_mix('/css/layouts/base.css')}}">
{{-- 页面样式插槽 --}} @stack('styles') </head>
<body> {{-- 头部 --}} @stack('layout.header') {{-- 内容 --}} @stack('layout.content') {{-- 尾部 --}} @stack('layout.footer')
{{-- 全局公共脚本 --}} @include('layouts.component.footer_script') <script src="{{ g_mix('/js/runtime.js') }}"></script> <script src="{{ g_mix('/js/lib.js') }}"></script> <script src="{{ g_mix('/js/common.js') }}"></script>
{{-- 页面脚本插槽 --}} @stack('scripts') </body> </html>
|
CSS 预处理
CSS 预处理放弃了传统的 Sass 或者 Less 而选择 PostCSS 因为他以插件的形式更灵活更接近下一代 CSS,可以随时根据应用的兼容要求调整插件面向未来编程,主要集成了以下插件:
- postcss-assets
- postcss-color-function
- postcss-cssnext
- postcss-extend
- postcss-import
- postcss-loader
- postcss-mixins
- postcss-nested
- postcss-preset-en
- postcss-simple-vars
语法转译与垫片
项目仅仅使用 @babel/env 的语法转译功能来支持 stage-3 阶段的语法,API 的垫片并没有采用其自带的方案来处理,而是根据 core-js@3
来根据目前的兼容要求自定义的垫片包,主要是为了防止滥用新 API 增加心智理解负担。
1 2 3 4 5 6 7 8
| { "presets": [ ["@babel/env", { "targets": ["last 2 versions", "ie >=9"], "useBuiltIns": false }] ], }
|
语法规范与检测
目前项目主要做了 JS 语法与 CSS 颜色变量检测,JS 语法检测主要是统一 JS 语法风格防止一些低级语法错误, CSS 颜色变量检测是规范 CSS 颜色的使用,有关颜色的值只能使用变量统一 UI 规范,品牌升级的时候可以快速调整。