Vue SPA + Nodejs project practice
The article links: blog.csdn.net/i348018533/…
This paper mainly describes the use of vue SPA single-page application background management system with NodeJS interface agent, and use webpack compiled project architecture sharing. Object oriented development is a certain front-end development of vue NodeJS Webpack have a certain understanding of the development.
Vue profile
introduce
- Bottom-up incremental design, easy to use and easy to integrate with other existing systems
- Componentized application building
- The same rich ecosystem enables complex applications
- Data-driven view
I think the above four points summarize the definition of VUE. Because the core library of VUE only focuses on the view layer, you can easily add VUE in the form of introduction to any existing project without incurring any intrusion into the existing system. At the same time, vUE also has a very rich ecological environment (VUE-Router, VUEX, WEEX, SSR), so vUE can also be used to build medium and large systems.
Some major improvements to VUE 2
- Virtual-DOM
- Compatible Templates, JSX
- SSR
What needs to be mentioned here is SSR. At present, the official SSR document only has English version, and the official recommendation is to use NUXT framework to easily achieve SSR.
If you want to implement SSR by yourself, it is not complicated. The main difficulty lies in how to write a set of general code that can run on both the client side and the server side. For example, window.location, document.getxxx and so on are used in the code. These objects only exist in the browser environment, the server nodeJS does not exist, and the server code cannot be rendered after vue instantiation, the server is only parsed into HTML text through the virtual DOM. Therefore, the server cannot be instantiated and mounted to the DOM like the client.
The solution is to define two sets of entry files in WebPack. The code that can only run on the client side is loaded in the client side, and the server side is also separate. Then, the two pieces of code are packaged as bundles through VueSSRServerPlugin and VueSSRClientPlugin, two webpack plug-ins officially provided by VUE.
The state management of VUE on SSR performance is not immutabled. In addition, because of the compilation process of virtual DOM to HTML, SSR performance has not been improved, but it can be made up by some caching strategies, if the project with strict requirements on the first screen time may be temporarily unacceptable.
More information about Vue is at cn.vuejs.org
The project structure
The back-end
The backend structure is very lightweight because its main role is to act as an interface proxy and authorization validation function, so it can be handled by a few simple middleware:
Before ->controllers ->auth ->proxy: Passing authentication allows the calling back-end interface to return the back-end stream directly to the front end through the request pipe catch: catch an error
The front end
This is a very typical vue SPA architecture, using VUE-Router to handle routing, Axios to handle asynchronous data requests, and iView as the UI framework. Currently iView is still in the 2.x RC version, not very well supported for JSX and SSR) because LoDash is so easy to use, since it is a back-end project should also be able to tolerate tens of KB more files.
The front-end directory structure is as follows:
Explanation:
- Components public component registered to a global instance:
- Config Indicates the configuration file of the project
- Custom directives used in the Directives project
- Custom filters used in the Filters project
- The Pages service directory is divided by the functions of service modules. Page-related components and stores are stored in corresponding page folders
- Plugins encapsulate routing, cache, auth, and other functions into a plug-in that is installed in an instance during vUE initialization
- Services Data processing, asynchronous data requests
- Static resource files — fonts, images, etc.
- Util helper classes
componentization
Design principles:
- reusable
- Abstract divide and conquer
- Black box
If a function is going to be used in more than one place then it must be made into a component, what a component needs to do and what it does, and that needs to be defined before you define the component, otherwise it gets more and more ambiguous.
Some do not reuse components and need to be abstracted, because the breakup of a page is componentized can effectively reduce the complexity of the page code, is an example of image: a 1000 lines of code file and 10 each only 100 lines of code in the file, after the complex function abstraction layers be split will gradually reduce the overall complexity.
A component is a black box state for the user, who needs to know what data the component needs to pass in, what functions the component has, and what data the component will output. The user does not need to care about the implementation logic inside the component.
The following can be seen with a page component partition:
The boxes on the different pages represent their hierarchy.
Routing management
history mode
The project uses the official Vue-Router library, which can easily realize nesting, peer routing, and support history mode. The default route is hash mode, that is, the user access path is in the following format
/#/home.htmlCopy the code
The history mode is the same as a normal URL:
/home.htmlCopy the code
The history mode is supported by the Web server. When accessing the /home/html path and the Web server returns 404 because it cannot find the path, all page requests in the Web server should output the entry file. nginx:
location / {
try_files $uri $uri/ /index.html;
}
location ~ \.html$ {
try_files $uri $uri/ /index.html;
}Copy the code
nodejs:
let mainProxy = (req, res) => {
res.header('x-version', env.version);
res.header('cache-control'.'no-store');
rp.get(`http://cdn.xxx.cn/${env.version}/index.html`).then(body => {
res.send(body);
});
};
router.get('/', mainProxy);
router.get(/\.html$/, mainProxy);Copy the code
Path Definition Route
The routing configuration for Vue is very simple:
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// Dynamic path parameters start with a colon
{ path: '/user/:id', component: User }
]
})Copy the code
It is only necessary to define the path in the path of each route. In addition, because the directory level of pages in the system is larger and deeper, it would be troublesome and difficult to maintain the path for each page. Therefore, each folder including sub-folders in the pages of the project provides an entry file. This entry file is responsible for merging each route into a routing tree object, and finally flattening the route number object to create a path with its own hierarchy:
Route definitions can be in three forms:
The simplest way to export a route
export default {
path: '/overview.html',
name: 'overview',
component: () => import(/* webpackChunkName: "home.overview" */'./overview'),};Copy the code
Custom hierarchy
export default {
list: {
path: '/onsale.html',
name: 'onsale',
component: () => import(/* webpackChunkName: "product.onsale" */'./onsale'),
meta: {
pageName: 'Goods on sale'}}};Copy the code
And an array of
export default [{
path: '/list.html',
name: 'list',
component: () => import(/* webpackChunkName: "repository.express" */'./list'),
meta: {
pageName: Warehousing logistics Management
}
}, {
path: '/:id.html',
name: 'info',
component: () => import(/* webpackChunkName: "repository.express" */'./info'),
meta: {
pageName: 'Warehousing Logistics Details'}}];Copy the code
Entry file:
import overview from './overview';
export default {
overview
};
Copy the code
The development of building
Hot Module Replacement
Scaffolding provided by VUE is hot updated by default so I won’t go into details here
DllPlugin
DLL Plugin is a webpack2 function, our system will generally update the release version through the webpack generated file with Hashcode to achieve a strong cache of files, but some packages will hardly be updated, such as: Vue, VUe-Router, Lodash, iView, etc. The change of hashcode during each release of the version causes that the client needs to download these packages once every upgrade, resulting in some unnecessary waste. The DLL plugin comes along. Now we can pack the packages that don’t update separately and generate a manifest file that tells WebPack: “These files are readily available, if you compile when you encounter a reference to these projects please skip over!” In the client, the DLL’s Hashcode does not change every time it is updated. Using the browser cache, the client only updates the files that actually need to be updated every time.
The build of the DLL is the same as the normal WebPack-packed build, but requires a special reference to the DllPlugin:
{
output: {
path: path.join(__dirname, '/dll'),
filename: '[name].[chunkhash:7].js',
library: '[name]_[chunkhash]'
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
entry: {
vendor: [
'vue'.'lodash'.'axios'.'vue-router'.'iview'.'moment'.'iview/dist/styles/iview.css'
],
},
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js'
}
},
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
plugins: [
new webpack.DllPlugin({ // The DllPlguin plug-in, which generates a manifeat.json manifest
path: path.join(__dirname, '/dll/manifest.json'),
name: '[name]_[chunkhash]',
context: __dirname,
}),
new UglifyJsparallelPlugin({
workers: os.cpus().length,
mangle: true,
compress: {
warnings: false
},
comments: false,
sourceMap: true
}),
new AssetsPlugin({ // Generate a JSON file that is used when referencing a DLL
filename: 'bundle-config.json',
prettyPrint: true,
fullPath: false,
path: path.join(__dirname, '/dll/')}),new ExtractTextPlugin({
filename: '[name].[contenthash:7].css'}})],Copy the code
It should be mentioned here that DllPlugin does not generate a list of result files, so it is necessary to manually import the resource files generated by DLLS when referencing DLLS. However, we added Hashcode into the DLL generation file, so the name of each reference may be different. So we use the AssetsPlugin plug-in to generate a separate list of packaged files.
Use the DllReferencePlugin in the project WebPack configuration file to tell WebPack what doesn’t need to be packaged, and then use the AddAssetHtmlPlugin to add the DLL’s resulting file to index.html.
let assets = _.map(bundleConfig.vendor, (asset, name) => {
return {
filepath: require.resolve(`./dll/${asset}`),
outputPath: utils.assetsPath(name),
typeOfAsset: name
}
})
plugins: [
new AddAssetHtmlPlugin(assets),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dll/manifest.json'),})]Copy the code
Lazy Loading
Vue-router supports loading views asynchronously and is very simple to implement: Import is already supported in Webpack 2.x because it is an ES proposal and is currently supported in stage-2. If you need to add syntax-dynamic-import to Babel configuration, Parser should also be changed to babel-esLint in the ESLint configuration if esLint is used to check syntax. WebpackChunkName in import is the syntax for declaring asynchronous package names, which is also the Magic comment supported by Webpack2.x
export default {
path: '/overview.html',
name: 'overview',
component: () => import(/* webpackChunkName: "home.overview" */'./overview'),};Copy the code
Code Generation
Because it is a management project, the structure of the page is mostly similar, so every time the development of a new page a lot of work is repeated, such as the definition of page VUE template content, registration routing, so in the project we wrote a very simple code generation tool, simple and useful.
npm run code -module=testmodule -page=testmodule -mode=lsitCopy the code
Using the above command, you can automatically generate a list template page and register the route. The opN component automatically opens the browser to locate the newly created page.
You can define many template files in the template folder, such as page sample files, page routes, and so on, by substituting placeholders in the file contents for the module and page names entered during the run command.
As for how to add the new module into the existing entry file, we used Acorn to parse the entry file into an AST tree. After adding the structure of the new module into the tree structure, we used EscodeGen to parse the AST tree into character codes and output them to the entry file.
Entry file:
After the treatment:
A couple of questions thrown up
Component independent debugging and unit testing
Debug components separately without testing components you must open the page where the components are located Unit tests of components above are not touched yet future plans will be slowly added to the project.
Image and font files packaged by DllPlugin
If there are static files such as pictures and fonts after DLL packaging, the only feasible way is to copy these resources when the project is compiled. What good way is there for the time being
Tree shaking for Webpack?
After the actual experiment, it is found that the root node export of the file can only be resolved by Webpack under multiple circumstances as stated on webpack’s official website, such as:
// This function isn't used anywhere
export function square(x) {
return x * x;
}
// This function gets included
export function cube(x) {
return x * x * x;
}Copy the code
Using one of the webpack functions in this case will automatically weed out the rest of the code, but that doesn’t seem to happen very often.
This is a summary of the architecture of the VUE project. Having only developed Angular before, I have some understanding of concepts such as bidirectional binding components. It is easier to get to know VUE, but vue itself has a very low learning curve and provides a well-structured Chinese document