This article is about VUe2. We are currently building an open source scaffolding based on VUe3. If you are interested, please come here to contribute code.
1. Break down requirements
Technology stack
-
Considering the technology stack of subsequent recruitment and existing staff, Vue was chosen as the framework.
-
The company’s main business is GIS and BIM, and usually develops some medium and large systems, so VUE-Router and VUEX are essential.
-
Abandoned the Element UI in favor of Ant Design Vue.
-
Tool library select LoDash.
Erect scaffolding
-
Build NPM private server.
-
Using the Node environment to develop CLI tools, refer to my own article “Building your own Scaffolding -” Elegant “Build front-end Engineering.
-
A template based on @vue/ CLI (which is well known and saves development time rather than building from scratch).
-
Define various functions (component libraries, state management, filters, directives, CSS built-in variables, CSS Mixins, form validation, utility functions, and so on) that may be used in development based on business requirements.
-
Performance optimizations, such as the Ant Design Vue component library.
The development of specification
-
Standardize code style, naming rules, and directory structure.
-
Specification for the use of static resources.
-
Unit test, submit online test specification.
-
Git commit records and multiplayer collaboration specifications.
Styles 2.
CSS preprocessor selection
- Sass/Scss ✅
- Less ✅
- Stylus ⭕
Why did you choose two? Since the company team tends to use SCSS development, less is designed to cover the Style of Ant Design Vue, which is the only style I like for stylus.
Local versus global styles
Local style
The scoped scheme is generally used:
<style lang="scss" scoped>
...
</style>
Copy the code
Global style
Global styles directory: @/styles
Variable. SCSS: global variables manage mixins. SCSS: global mixins manage global. SCSS: global styles
Variable. SCSS and mixins. SCSS are loaded before global.css and can be used anywhere in the project without import.
// vue.config.js
module.exports = {
css: {
loaderOptions: {
sass: {
prependData: ` @import '@/styles/variable.scss'; @import '@/styles/mixins.scss'; `,},},},}Copy the code
Experience optimization
Page loading progress bar
Use nProgress to create a pseudo progress bar for routed jumps. This allows users to know that the page is loading in case of a bad network:
import NProgress from 'nprogress';
router.beforeEach(() = > {
NProgress.start();
});
router.afterEach(() = > {
NProgress.done();
});
Copy the code
Beautify scroll bar
I’ve been using my Mac as the front end, and suddenly I noticed that my colleague’s Windows had a very ugly scroll bar. To be consistent:
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
width: 6px;
background: rgba(#101F1C.0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: rgba(#101F1C.0.5);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(#101F1C.1);
}
Copy the code
Static resource load page
Loading the page for the first time will cause a large amount of white screen time, so it may seem friendly to do a loading effect. In fact, it is easy to simply write some static styles in public/index.html.
Mobile 100VH problem
When using 100vh on mobile terminal, it is found that in Chrome and Safari browsers, the browser bar, some navigation bars and link bars lead to different rendering:
You think 100vh === viewport height
Actually 100vh === viewport height + height of the browser toolbar (address bar, etc.)
The solution
Vh-check NPM install vh-check –save
import vhCheck from 'vh-check';
vhCheck('browser-address-bar');
Copy the code
Define a CSS Mixin
@mixin vh($height: 100vh) {
height: $height;
height: calc(#{$height} - var(--browser-address-bar, 0px));
}
Copy the code
And then you can’t order anything.
3. The component library
Because the Element UI had not been updated for a long time and had previously used React Ant Design (emphasis), the component library chose Ant Design Vue.
Overrides the Ant Design Vue style
Ant Design === ‘ugly’ in the eyes of designers.
1. Use.less files
Ant Design Vue styles use Less as the development language and define a series of global/component style variables, so you need to install Less, less-loader, and override the default styles at @/styles/antd-theme.
Advantages are:
Easy and quick, you can change the class to override the default variable.
The disadvantage is that:
@import ‘~ant-design-vue/dist/antd.less’ must be introduced; , all component styles will be introduced, resulting in a packaged CSS volume of about 500kb.
2. Use JavaScript objects
The built-in variables can be modified as JavaScript objects, requiring Less to be configured:
// vue.config.js
const modifyVars = require('./src/styles/antdTheme.js');
module.exports = {
css: {
loaderOptions: {
less: {
lessOptions: {
javascriptEnabled: true,
modifyVars,
},
},
},
},
}
Copy the code
This step can be further optimized by using babel-plugin-import to make Ant Design Vue component styles loadable on demand:
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',].plugins: [['import',
{ libraryName: 'ant-design-vue'.libraryDirectory: 'es'.style: true},]],};Copy the code
Advantages are:
You can import on demand, and the size of the packaged CSS depends on how many components you reference.
The disadvantage is that:
You cannot use class for style overwriting.
Kill useless ICONS
Ant Design Vue introduces all the ICONS at once (no matter how many components you use), resulting in hundreds of kilobytes of ICONS packaged in volume. Most of these ICONS will not be adopted by designers, so some should be eliminated:
Create an icons.js to manage Ant Design Vue ICONS, using a Loading icon as an example:
// @/src/assets/icons.js
export { default as LoadingOutline } from '@ant-design/icons/lib/outline/LoadingOutline';
Copy the code
How do I know what path the icon you want to load is in?
In the @ant-design/ ICONS /lib directory there are three styles of ICONS: Fill, Outline and Twotone. The internal files are not in SVG format, but in JS and TS format, which is why we can introduce ICONS in this way.
The next step is to import this file by configuring vue.config.js:
// vue.config.js
module.exports = {
configureWebpack: {
resolve: {
alias: {
'@ant-design/icons/lib/dist$': path.resolve(__dirname, './src/assets/icons.js'),},},},}Copy the code
Fix Moment
The Ant Design Vue is still quite large. This is because the Ant Design Vue is heavily dependent on the plugin, so we use the webpack plugin to reduce the package size. Here we only keep the zh-CN language package:
// vue.config.js
module.exports = {
chainWebpack: (config) = > {
config
.plugin('ContextReplacementPlugin')
.use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/./zh-cn/]); }},Copy the code
Some components need to be referenced within the page
Some components with large volume in Ant Design Vue, such as DatePicker, should be loaded in the page according to service requirements, so as to ensure the first screen loading speed as much as possible:
<script>
import { DatePicker } from 'ant-design-vue';
export default {
components: {
ADatePicker: DatePicker,
},
}
</script>
Copy the code
4. Static resources and ICONS
Static resource
All static resource files are uploaded to Ali Cloud OSS, so they are differentiated in environment variables.
The VUE_APP_STATIC_URL attributes of.env.development and.env.production configure the local static resource server address and the online OSS address, respectively.
The local static resource server is created by pM2 + HTTP-server, and the designer can just throw it in.
Automatically registers Svg ICONS
In daily development, there are always a lot of ICONS to use, so we chose to use SVG ICONS directly. But wouldn’t it be a hassle to have to navigate to that icon every time you used it?
The following is the solution I want (just name equals filename) :
<template>
<svg name="logo" />
</template>
Copy the code
And the final package needs to be merged into a Sprite picture.
First of all, the SVG ICONS in the @/ Assets/ICONS folder need to be automatically registered, and the relevant Settings of Webpack and SVG-sprite-loader need to be set, and all files are packaged as SVG-Sprite.
module.exports = {
chainWebpack: (config) = > {
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end();
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader'); }},Copy the code
Write a global Vue component
// @/components/m-svg/index.js
const requireAll = (requireContext) = > requireContext.keys().map(requireContext);
const req = require.context('@/assets/icons'.false./\.svg$/);
requireAll(req);
Copy the code
@/components/m-svg/index.vue
<template> <svg class="mw-svg" aria-hidden="true"> <use :xlink:href="iconName"></use> </svg> </template> <script> export default { name: 'm-svg', props: { name: { type: String, default: '' }, }, computed: { iconName() { return `#${this.name}`; ,}}}; </script> <style lang=" SCSS "scoped>. Mw-svg {width: 1.4em; Height: 1.4 em. fill: currentColor; overflow: hidden; line-height: 1em; display: inline-block; } </style>Copy the code
Parameter name
- Type: String
- Default value: null
- Description: Placed in
@/assets/icons
The file name of the folder
style
- The size of the icon can be changed using the width + height property.
- Change width/height = font-zise * 1.4 by changing the font-size property
5. Asynchronous request
Encapsulation Axios
Encapsulate Axios in @/libs/request.js path, encapsulating request parameters, request headers, error messages, Request interceptor, Response interceptor, unified error handling, baseURL Settings, etc.
Nonsense does not say directly paste code:
import axios from 'axios';
import get from 'lodash/get';
import storage from 'store';
// Create an axios instance
const request = axios.create({
// The default prefix for API requests
baseURL: process.env.VUE_APP_BASE_URL,
timeout: 10000.// Request timeout
});
// Exception interception handler
const errorHandler = (error) = > {
const status = get(error, 'response.status');
switch (status) {
/* eslint-disable no-param-reassign */
case 400: error.message = 'Request error'; break;
case 401: error.message = 'Not authorized, please log in'; break;
case 403: error.message = 'Access denied'; break;
case 404: error.message = 'Error requesting address:${error.response.config.url}`; break;
case 408: error.message = 'Request timed out'; break;
case 500: error.message = 'Server internal error'; break;
case 501: error.message = 'Service not implemented'; break;
case 502: error.message = 'Gateway error'; break;
case 503: error.message = 'Service unavailable'; break;
case 504: error.message = 'Gateway timed out'; break;
case 505: error.message = 'HTTP version not supported '; break;
default: break;
/* eslint-disabled */
}
return Promise.reject(error);
};
// request interceptor
request.interceptors.request.use((config) = > {
// If the token exists
// Allow each request to carry a user-defined token. Change the token based on the actual situation
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `bearer ${storage.get('ACCESS_TOKEN')}`;
return config;
}, errorHandler);
// response interceptor
request.interceptors.response.use((response) = > {
const dataAxios = response.data;
// This status code is specified with the back end
const { code } = dataAxios;
// Determine by code
if (code === undefined) {
// If there is no code, this is not the project backend development interface
return dataAxios;
// eslint-disable-next-line no-else-return
} else {
// Code indicates that this is a back-end interface for further evaluation
switch (code) {
case 200:
// [Example] Code === 200 indicates no error
return dataAxios.data;
case 'xxx':
// [Example] Other code specified by background
return 'xxx';
default:
// Not the correct code
return 'Not the correct code';
}
}
}, errorHandler);
export default request;
Copy the code
- Use VUE_APP_BASE_URL to distinguish the API addresses of the line and the development environment.
- Code plays a critical role, such as verification when tokens expire.
- A package called Sotre is used as a locally stored tool to store tokens.
Cross-domain problem
If you don’t want to bother them, you can use a proxy provided by devServer:
// vue.config.js
devServer: {
proxy: {
'/api': {
target: 'http://47.100.186.132/your-path/api'.ws: true.changeOrigin: true.pathRewrite: {
'^/api': ' '}}}}Copy the code
The Mock data
A very common situation is that the back end interface doesn’t come out, and the front end is just staring.
The Mock data function is based on mock.js (open New Window) and automatically loads the Mock configuration file via Webpack.
The rules
- All mock configuration files should be placed in
@/mock/services
Within the path. - in
@/mock/services
You can create folders related to services to store configuration files. - All configuration files should follow
***.mock.js
Create a naming convention for. - The configuration file is exported using the ES6 Module
export default
或export
An array.
Entrance to the file
import Mock from 'mockjs';
Mock.setup({
timeout: '500-800'});const context = require.context('./services'.true./\.mock.js$/);
context.keys().forEach((key) = > {
Object.keys(context(key)).forEach((paramKey) = >{ Mock.mock(... context(key)[paramKey]); }); });Copy the code
The sample template
import Mock from 'mockjs';
const { Random } = Mock;
export default [
RegExp('/example.*'),
'get',
{
'range|50-100': 50.'data|10': [{/ / the only ID
id: '@guid()'.// Generate a Chinese name
cname: '@cname()'.// Generate a URL
url: '@url()'.// Generate an address
county: Mock.mock('@county(true)'),
// Select a value at random from the array
'array|1': ['A'.'B'.'C'.'D'.'E'].// Randomly generate a time
time: '@datetime()'.// Generate an image
image: Random.dataImage('200x100'.'Mock Image'),},],},];Copy the code
6. The routing
Layout
The layout is temporarily divided into three categories:
-
FrameIn: A route based on BasicLayout that usually requires login or permission authentication.
-
FrameOut: Routes that do not require dynamic permissions, such as login pages or common pages.
-
ErrorPage: For example, 404.
Permission to verify
By obtaining the permission of the current user to compare routing tables, a routing table accessible to the current user is generated and dynamically mounted to the router through router.addRoutes.
- Check whether the login status is required. If so, go to /user/login
- If no token exists in the local storage, switch to /user/login
- If token exists and user information does not exist, vuex ‘/system/user/getInfo’ is automatically called.
In routing, the function of permission verification is integrated. If you need to add permission to a page, add the corresponding key under META:
auth
- Types: Boolean
- Note: When auth is true, this page requires login permission validation, only for the frameIn route.
permissions
- Type: the Object
- Permissions Each key can be used to authenticate the permissions function. If the key is true, the permissions function is enabled
v-permission
Directive to hide the corresponding DOM.
Here is the code for verifying permission when routing jumps:
import router from '@/router';
import store from '@/store';
import storage from 'store';
import util from '@/libs/utils';
/ / the progress bar
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
const loginRoutePath = '/user/login';
const defaultRoutePath = '/home';
/** * Route interception * permission validation */
router.beforeEach(async (to, from, next) => {
/ / the progress bar
NProgress.start();
// Verify that all matches of the current route require login authentication
if (to.matched.some((r) = > r.meta.auth)) {
// Whether token exists as a condition to verify login
const token = storage.get('ACCESS_TOKEN');
if(token && token ! = ='undefined') {
// Whether the login page is displayed
if (to.path === loginRoutePath) {
next({ path: defaultRoutePath });
// Query whether to store user information
} else if (Object.keys(store.state.system.user.info).length === 0) {
store.dispatch('system/user/getInfo').then(() = > {
next();
});
} else{ next(); }}else {
// The login page is displayed when there is no login
// Carry the full path to the page to jump to after successful login
next({
name: 'Login'.query: {
redirect: to.fullPath, }, }); NProgress.done(); }}else {
// No authentication is requirednext(); }}); router.afterEach((to) = > {
/ / the progress bar
NProgress.done();
util.title(to.meta.title);
});
Copy the code
Page development
- According to service requirements, create corresponding page components in views in the form of folders according to routing levels, and create index.vue files in the folders as the entry files of the page.
- Components in the page: Create the Components folder under the page folder and create the corresponding component files inside it. If it is a complex component, create the component as a folder.
- Tool module: a tool module capable of high abstraction, should be created in @/ SRC /libs create JS files.
7. Build and optimize
Package analysis tool
After building the code, what exactly takes up so much space? Guess intuitively or use Webpack-bundle-Analyzer.
const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');
module.exports = {
chainWebpack: (config) = > {
if (process.env.use_analyzer) {
config
.plugin('webpack-bundle-analyzer') .use(WebpackBundleAnalyzer.BundleAnalyzerPlugin); }}};Copy the code
Open the Gzip
That’s right, the back end will have to support your.gz files, and all you have to do is sit back and wait for your boss to say:
chainWebpack: (config) = > {
config
.plugin('CompressionPlugin')
.use(CompressionPlugin, []);
},
Copy the code
Route lazy loading
This @vue/ CLI has been helped to deal with, but we also need to know its principle and how to configure.
{
path: 'home'.name: 'Home'.component: () = > import(
/* webpackChunkName: "home" */ '@/views/home/index.vue'),},Copy the code
The webpackChunkName comment is still worth adding, at least you’ll know which pages are stinky and big after you pack them.
Preload & Prefetch
Not clear about these two features to go @vue/ CLI make-up lessons, these two features are very helpful for you to handle load performance.
8. Test the framework
It uses the official Vue Test Utils directly, which is a great way to Test components.
Writing unit tests is actually very difficult to promote in the team, I don’t know what you think.
9. The component library
For many third-party tools, I insist that repackaging as vUE plugins does not cost much to develop and gives you flexibility in subsequent development.
I have packaged the following libraries as vUE plug-ins and submitted them to NPM private server:
- Digital animation
- Code highlighting
- Large file upload (slice, resumable, second) needs to work with the back end
- Preview picture
- Excel import and Export
- Rich text editor
- Markdown editor
- Code editor
Large file upload interested can leave a message, I carry out a detailed follow-up to write this piece.
10.Vuex
There are some built-in functions, mainly to do some encapsulation of the following functions:
-
User information management (storing information, manipulating tokens, etc.)
-
Login (tuning interface)
-
Menu management (routing information storage, menu generation, fuzzy query and other functions)
-
UA information
-
Full screen operation
-
Loading
-
Log management (message reminder, log retention, and log reporting)
11. The filter
Filters are a nice feature provided by Vue. I heard that VUe3 is gone?
{{ message | capitalize }}
Copy the code
I’ve written a few commonly used filters:
- Date/time
- For the rest of
- Links to differentiate environments (mainly for local static resource servers and OSS)
- The file size
- Digital amount
- Floating point accuracy
12. The instructions
Custom directives can be very helpful:
- Component permission validation
- Text copied
- Shortcut key binding
- Scroll to the specified position
- Lazy loading of images
- The focus of
13. Development specifications
ESLint
Whether it’s a multi-person collaboration or a personal project, code specification is important. Doing so not only largely avoids basic syntax errors, but also keeps the code readable.
Here we use Airbnb JavaScript Style Guide.
This specification gives me the feeling is very strict!
The CSS specification
Reduce selector complexity
The browser reads the selector from the right to the left.
#block .text p {
color: red;
}
Copy the code
- Find all P elements.
- Find if the element in result 1 has a parent element with the class name text
- Find if the element in result 2 has a parent element whose ID is block
Selector priority
Inline > ID selector > Class selector > Label selector
- The shorter the selector, the better.
- Try to use higher-priority selectors, such as ID and class selectors.
- Avoid the wildcard character *.
Using flexbox
In the early days of CSS layouts, elements could be positioned absolutely, relative, or floating. Now, we have flexbox, which has one advantage over the previous layout: better performance. Flexbox compatibility is a bit of a problem though, and not all browsers support it, so use it with caution. Compatibility between browsers:
- Chrome 29+
- Firefox 28+
- Internet Explorer 11
- Opera 17+
- Safari 6.1+ (prefixed with -webkit-)
- The Android 4.4 +
- IOS 7.1+ (Prefixed with -webkit-)
Animation performance optimization
Transforms and opacity property changes do not trigger rearranges or redraws in CSS. They are properties that can be handled separately by a composite.
Attribute values
- If the value is a decimal between 0 and 1, you are advised to omit 0 from the integer.
- If the length is 0, omit the unit.
- It is not recommended to use named color values.
- It is recommended that when an element needs to be raised to contain internal floating elements, clearfix it by setting clear on the pseudo-class or triggering the BFC. Try not to add empty labels.
- With the exception of common styles, do not use them in business code! Important.
- It is recommended to layer the Z-index to manage the visual hierarchy of absolutely positioned elements outside the document flow.
Font typesetting
- The font size should not be less than 12px (PC).
- It is recommended that the font-weight attribute be described numerically.
- Line-height should be used as a numeric value when defining text paragraphs.
Vue code specification
conventional
-
When using the data attribute in a component (anywhere except new Vue), its value must be the function that returns an object data() {return {… }} instead!
-
The definition of prop should be as detailed as possible, or at least specify its type.
-
Attribute of a Boolean type. If true, the value of the attribute is written.
-
Do not operate on vUE variables in computed.
-
Parent component communication should take precedence over this.$parent or changing prop via prop and events.
-
A key must always be used with v-FOR on a component to maintain the state of the internal component and its subtrees.
-
V-if and V-for cannot be used together
-
Public methods should not be attached to prototypes. They can be written in utils files or mixin files. Do not register business common components globally.
-
Do not mount any third-party plug-ins to the Vue prototype.
-
Highly general-purpose methods are encapsulated in liBS, global components, or instruction sets.
-
Set the scope for the component style.
-
Use abbreviations whenever possible.
vuex
State (Opens new Window) : a single State tree, in which arrays, objects, strings, etc., need to be defined, the vUE component can obtain the State of the object you define.
- Modify the
state
Data must passmutations
. - Everything that could change
state
One or more must be created synchronously to change itmutations
. - The data obtained by the server is stored in
state
Is retained as original data and cannot be changed.
Getters (opens new Window) is a bit like the vue.js calculation property. When we need to derive some state from store state, we use Getters, which accepts state as the first argument. Furthermore, the return value of getters is cached based on its dependencies and recalculated only when the dependency value in getters (a value in state that requires derived state) changes.
-
Process the data format you need to get through getters, not by modifying the state raw data.
-
MapGetters are not mandatory within components, because you may need to use getters and setters.
-
The only way to change state is to submit mutations.
-
The mapMutations helper function is used within the component to map the methods in the component to the store.mit call.
-
Use the uppercase + underscore rule for naming.
-
Define CLEAR to ensure that data can be initialized during route switching.
Actions
- The page – heavy data interface should be invoked in actions as much as possible.
- The data returned by the server is not processed as much as possible and the original data is retained.
- The data obtained must be called
mutations
changestate
.
Modules
- Modules are normally divided by page (opens new window).
- It’s built in by default
system
Ensure the basic function of scaffolding. - Add attributes for each page module or submodule of the page
namespaced: true
.
14. Complete detailed usage documentation
Tools, whether functionality, component libraries, etc., need to be well documented to refer to, and even wheel builders can’t resist forgetting many details over time.
Here I use VuePress to build the document quickly and easily.
VuePress + Travis CI + Github Pages
Git multi-person collaboration process
The company uses in-house GitLab managed code
Root warehouse
At project startup, the project manager sets up the original repository, called the Root repository.
The source repository has a function:
- Summarize the code of the various developers involved in the project.
- Store code that is stable and releasable.
- Submitting Merge Requests to the Master branch triggers a test environment build (CI/CD).
- The source repository is protected and developers cannot work directly on it.
Developer repository
No developer has direct access to the Root repository, and once the repository is built, all a developer needs to do is Fork out a portion of the repository for their daily development.
- The repository forked by each developer is completely independent of each other.
- Each developer submits the code to his own repository, and when the work is done, the developer can send a Pull Request to the source repository, and the local repository will merge the source repository first to resolve conflicts.
- Initiate a Merge Request asking the administrator to Merge their own code into the master or other branches of the source repository.
Git process
- The front end project will be in
Root
Create a warehousedev
Branches, used for pulling and merging code, and if there are multiple different test environments, create branches according to the test environment. - Create yours in a local repository
dev
Branches and other functional branches. - Development process is not allowed directly in
master
Branch development, create a new branch to develop,Git checkout - b {branch_name}
. - Standard and detailed writing
commit
, recommended usegit-cz
Tools for submission. - After the completion of the development of the corresponding branch into their own repository
master
Branch. - will
master
branchpush
Go to your own remote repository (Fork repository). - to
Root
warehousedev
Branch to submitMerge Requests
. - Remind the front-end lead to review code, resolve conflicts, or bring the test environment online.
- After conflict resolution
git pull upstream dev
Pull the latest code after solution.