For koA-GRACE V1.x: github.com/xiongwilee/…
Gracejs(also known as KOA-Grace V2) is a new MVC+RESTful architecture based on KOA V2.x.
A list,
Gracejs is an upgraded version of KOA-Grace, also known as KOA-Grace V2.
Key features include:
- Support MVC architecture, which can generate server-side routing more easily;
- Standard RESTful architecture, support back-end interface asynchronous concurrency, better page performance;
- A set of Node environment services multiple site applications, deployment is simpler;
- Elegant MOCK function, the development environment to simulate data more smoothly;
- Perfect support for async/await and Generator syntax.
- More flexible front-end build types, use whatever you want, whatever you want.
Compared to KOA-GRACE V1 (KOA-GRACE v1) : Gracejs perfectly supports KOA V2, and has improved the performance of virtual host matching and route matching, as well as some test cases. Of course, don’t worry if you are using KOA-Grace, we will port the performance and functionality of Gracejs in addition to koA2 support to the corresponding MIDDLEWARE of KOA-Grace.
Concepts such as “front-end and back-end separation”, “RESTful” and “MVC” will not be introduced here. If you are interested, you can refer to the paper on the practice of front-end and back-end separation of Qudian based on KOAJS.
Gracejs and front and rear end separation problem ac group:
- Wechat communication group: Add wechat
xiongwilee
After (note: city – occupation – name) pull you to “Gracejs and front and back end separation communication” wechat group - QQ communication group of: 368463457 (this group will be disused, you can contact to add wechat
xiongwilee
Pull to wechat group)
Start fast
Note: Make sure you have Nodejs at least v7.6.0 in your environment (or consider koA-Grace V1.x with Nodejs V4.x + support)
Execute command:
$ git clone https://github.com/xiongwilee/Gracejs.git
$ cd Gracejs && npm installCopy the code
BTW: NPM install can be unstable for well-known reasons and is recommendedcnpmThe installation.
run
Then, execute the command:
$ npm run devCopy the code
Then visit: http://127.0.0.1:3000 to see an example!
See github.com/xiongwilee/… App/Demo directory under the example, detailed explanation Gracejs MVC+RESTful architecture implementation.
The implementation of Gracejs has also been briefly described in previous articles (github.com/xiongwilee/…). However, considering the differences of Gracejs, detailed explanations are made from the three key points of directory structure, MVC model implementation and proxy mechanism.
The directory structure
Gracejs has exactly the same directory structure as koA-Grace V1.x:
.├ ─ ─ controller │ ├ ─ ─ data. Js │ ├ ─ ─ defaultCtrl. Js │ └ ─ ─ home. Js ├ ─ ─ the static │ ├ ─ ─ CSS │ ├ ─ ─ image │ └ ─ ─ js └ ─ ─ views └ ─ ─ home.htmlCopy the code
Among them:
controller
Used to store routing and controller filesstatic
Used to store static filesviews
Used to store template files
It is important to note that this directory structure is the standard directory structure for production code. You can adjust your directory structure as much as you like in the development environment, as long as you make sure that the compiled output files are printed in this path.
If you still have questions about this, check out: Front-end construction -Boilerplate
In order to meet more usage scenarios, Gracejs has added the function of simple Mongo database.
But to be exact, the separate Nodejs frameworks at the front and back end are VC architectures without a Model layer. Because the backend separation framework should not have any database, SESSION storage functions.
As shown in the figure above, the specific process is as follows:
- First, Nodejs Server (also known as Gracejs service) listens for user requests.
- In the second step, Gracejs Middlewares process the request context.
- Step 3: Enter the Controller according to the path and method currently requested.
- The fourth step is to obtain data from the backend in proxy mode through HTTP request.
- Step 5: Splicing data and rendering templates.
The fourth step, proxy mechanism, is the core part of Gracejs to achieve the separation of front and back ends.
The proxy mechanism
Take the realization of an e-commerce application under the “personal center” page as an example. Assume that the first screen of this page includes: user basic information module, commodity and order module, message notification module.
Once the back-end architecture is servified, these three modules can be decoupled into three HTTP API interfaces. Gracejs’ this.proxy method is used to asynchronously and concurrently fetch data from the three interfaces.
The diagram below:
This has several benefits:
- In Nodejs layer (server) asynchronously and concurrently obtain data from the back end (server), so that HTTP can go through the Intranet and the performance is better.
- The back-end interface can be provided to the client at the same time, so that the interface can be reused to Web+APP, and the back-end development cost is lower.
- Once the data is fetched in the Nodejs layer and handed directly to the page, regardless of the technology stack used on the front end, the first-screen experience is much better.
So, is that perfect? Surely not:
- How to ensure the security of a back-end interface after it is opened to the Internet?
- What if the current page request is a GET method but I want to POST to the back end?
- What if I want to reset the POST parameter at the Controller layer?
- How does the back-end interface set cookies to the browser?
- How can SESSION state not be lost after passing through a layer of Nodejs proxies?
- What if the current request is a file file stream? .
The good news is that these issues have been addressed in proxy middleware. Koa-grace-proxy can be found at github.com/xiongwilee/… .
4. Detailed user manual
Before looking at the detailed user manual, it is recommended to take a look at the main Gracejs file source: github.com/xiongwilee/… .
I won’t waste space to post code here, but Gracejs is a collection of key middleware components.
All middleware is in the Middleware directory, and the configuration is managed by config/main.*.js.
About configuration files:
- Json merge field > config/main.*.js > config.js;
- Configure the global scope saved under Gracejs after generation
global.config
, easy to read.
Here are the roles and uses of several key middleware components.
Vhost here can be understood as a Gracejs Server serving several sites. Gracejs supports mapping by host and host+ level 1 PATH. In fact, a domain name (or a domain name + path level) corresponds to an application, and an application corresponds to a directory.
Note: Considering the performance of the re, the Vhost does not consider the re mapping.
Reference config/main. Development. Js, so to configure vhost:
//Vhost configuration
vhost: {
'127.0.0.1':'demo'.'127.0.0.1 / test':'demo_test'.'localhost':'blog',}Copy the code
Demo,demo_test and blog correspond to three directories under app/ respectively. You can also specify the directory path by modifying the path.project configuration in the configuration file:
//Path-related configurations
path: {
// project
project: './app/'
}Copy the code
Router — Routing and controller
The route generation method in Gracejs is very simple. Take the built-in Demo module as an example. Go to the Controller directory of the Demo module: APP/Demo/Controller.
The file directories are as follows:
├─ ├─ class.js ├─ Class.js
Copy the code
The router middleware finds all.js files in the module and generates a route based on the file path and module.exports.
For example, the home.js file in the Demo module:
exports.index = async function () {
await this.bindDefault(a);await this.render('home', {
title: 'Hello , Grace!'
});
}
exports.hello = function() {this.body = 'hello world!'
}Copy the code
/home/index, /home, and /home/hello routes are generated. A few points need to be made:
- If the route is based on
/index
At the end, Gracejs will “give” one away/index
The same route of; - If the current file is a dependency, only referenced by other files; Then, configure it in a file
exports.__controller__ = false
, the file will not generate routes; referencedefaultCtrl.js
- The controller function here could be
await/async
orgenerator
Delta function, it could be a normal function; Gracejs is recommendedawait/async
; - It is also possible to wrap routing files in a directory, you can refer to:
app/blog
Controller file in; - If the current file route is a separate controller
module.exports
Return an arbitrary function.
Finally, if the user is accessing a route that cannot be found, the router defaults to looking for the /error/404 route, rendering the error/404 page if it exists (it is not redirected to error/404), and returning 404 if it does not.
2. Instructions for using routing files
To extend home.js in the Demo module:
exports.index = async function () {
.
}
exports.index.__method__ = 'get';
exports.index.__regular__ = null;Copy the code
In addition, the following points need to be noted:
- If dashboard/ POST /list requests need to be configured as
DELETE
Method, declared in post.jsexports.list.__method__ = 'delete'
(Do not declare the default injection of get and POST methods); - To configure more flexible routes
exports.list.__regular__ = '/:id';
Ok, and then pass through the controllerthis.params.id
To obtain the ID value, see:koa-router#named-routes - Note that: if
__regular__
If the regular expression is configured, the default route of the current controller and the route matching the regular are generated
Of course, if all the controller methods in the routing file are POST methods, you can add: module.exports.__method__ = ‘post’ at the bottom of the controller file, and the same for __regular__ configuration.
Note: In general, no additional configuration is required here, so don’t write it without special usage scenarios to keep the code beautiful__method__
and__regular__
Configuration.
3. Controller
To extend the index method of home.js in the demo module:
exports.index = async function () {
//Bind default controller methods
await this.bindDefault(a);//To get the data
await this.proxy(.)
//Render target engine
await this.render('home', {
title: 'Hello , Grace!'
});
}Copy the code
It’s a standard controller. The scope of this controller is the current KOA context, and you can use any method of the KOA context.
The instructions for using the key context attributes are as follows:
Koa comes with:
For more information about koA’s own context properties, see koajs’ official website: koajs.com/
The context properties | type | instructions |
---|---|---|
this.request.href |
String |
The full URL of the current page can also be shortened tothis.href |
this.request.query |
object |
The get argument can also be abbreviated asthis.query |
this.response.set |
function |
Set the response header, which can also be abbreviated asthis.set |
this.cookies.set |
function |
To set cookies, see:cookies |
this.cookies.get |
function |
To obtain cookies, refer to:cookies |
Gracejs injection:
The context properties | type | The middleware | instructions |
---|---|---|---|
this.bindDefault |
function |
router | The common controller is equivalent torequire('app/*/controller/defaultCtrl.js') |
this.request.body |
object |
body | The post argument can be obtained directly from this.request.body |
this.render |
function |
views | For the Template engine rendering method, see Template engine – Template Engine |
this.mongo |
function |
mongo | For details about how to operate a Database, see Database-database |
this.mongoMap |
function |
mongo | For the parallel Database multiple operations, see Database-database |
this.proxy |
function |
proxy | For RESTful data request methods, see Data Broker |
this.fetch |
function |
proxy | For the method of exporting files from the server, see request proxy |
this.backData |
Object |
proxy | By default, THE JSON data returned by the this.proxy back end is stored in Obejct format |
this.upload |
function |
xload | For details about how to upload files, see File Upload and Download |
this.download |
function |
xload | For details about how to download files, see File Upload and Download |
4. Write asynchronous functions in the controller
If there are other asynchronous methods in the controller, they can be implemented through promises. Such as:
exports.main = async function() {
await ((test) = > {
return new Promise((resolve.reject) = > {
setTimeout(() = > { resolve(test) }, 3000)
});
})('test')}Copy the code
Gracejs supports two data broker scenarios:
- Simple data proxy, arbitrary request to back-end interface, and then return JSON data (including file stream request to back-end, back-end return JSON data);
- A file proxy that requests a back-end interface and returns a file (such as a captcha image);
The following describes the use of the two proxy modes one by one.
1. Data broker
Data proxies can use the this.proxy method in the controller:
this.proxy(object|string,[opt])Copy the code
The this.proxy method returns a Promise, so you can use async/await or Generator for asynchronous concurrency depending on the type of the current Controller. Such as:
Async/await:
exports.demo = async function () {
await this.proxy({ / *.* /})}Copy the code
The Generator:
exports.demo = function * () {
yield this.proxy({ / *.* /})}Copy the code
To simplify the syntax, you can get the data directly in the backData field in the context after executing this.proxy. Such as:
exports.demo = async function () {
await this.proxy({
userInfo:'github:post:user/login/oauth/access_token? client_id=****',
otherInfo:'github:other/info? test=test',})console.log(this.backData);
/ * *
* {
* userInfo : {... },
* otherInfo : {... }
*}
* /
}Copy the code
The same goes for Generator methods.
In addition, if you want to get the proxy request header information, you can get it in the proxy method return content, for example:
exports.demo = async function() {let res = await this.proxy({
userInfo:'github:post:user/login/oauth/access_token? client_id=****',
otherInfo:'github:other/info? test=test'});console.log(res);
/ * *
* {
* userInfo : {
* statusCode: {... } // return HTTP status code
* request: {... } // Request body
* headers: {... } // Response header information
* body: {... } // Unprocessed Response body
*},
* otherInfo : {... }
*}
* /
}Copy the code
Usage Scenario 1: Proxy for multiple data requests
It can be found that the above case is a proxy scheme for multiple data requests at the same time, which is the realization of asynchronous concurrent data acquisition. Implementing multiple asynchronous concurrent requests for data using the this.proxy method is very simple:
exports.demo = async function() {await this.proxy({
userInfo:'github:post:user/login/oauth/access_token? client_id=****',
otherInfo:'github:other/info? test=test'});console.log(this.backData);
/ * *
* {
* userInfo : {... },
* otherInfo : {... }
*}
* /
}Copy the code
The result of the proxy is then injected into the context’s this.backData object by default.
To implement an interface request proxy, write:
exports.demo = async function() {await this.proxy('github:post:user/login/oauth/access_token? client_id=****');
}Copy the code
The data returned by the proxy request will then be assigned directly to this.body, which will return the request directly to the client.
instructions
github:post:user/login/oauth/access_token? Client_id =**** The description is as follows:
github
: for in theconfig/main.*.js
theapi
Object to configure;post
: is the request method requested by the data agent. This parameter can be omitted, and the default value is method requested by the current user.path
The query argument in the following request path overrides the request parameter of the current page (this.query), passing the query along with the request interface- You can also write the full path:
{userInfo:'https://api.github.com/user/login?test=test'}
In addition, the parameter description for this.proxy is as follows:
Parameter names | type | The default | instructions |
---|---|---|---|
dest |
Object |
this.backData |
Specifies the object to receive data from. Default isthis.backData |
conf |
Obejct |
{} |
Enclosing the proxy is usedrequestjsThis is the reset configuration passed to request (you can set the interface timeout here:conf: { timeout: 25000 } ) |
json |
Object |
{} |
To specify data in JSON format, see:Requestjs json configuration |
form |
Object |
{} |
Specify application/ X-www-form-urlencoded data,Requestjs form configuration |
body |
Buffer|String|ReadStream |
There is no | Reference:Requestjs body configuration |
headers |
Object |
{} |
Specifies the headers for the current request |
onBeforeRequest |
Function |
There is no | The callback event before each request is sent, scoped to the current context, has two parameters ‘proxy name’,’requestOpt’, can be operatedrequestOpt To modify requestJS parameters |
onAfterRequest |
Function |
There is no | The callback event after each request gets the result, scoped to the current context, has two parameters ‘proxy name’,’response’, can be operatedresponse To modifythis.proxy Return result of |
If the json, form, and body parameters are not sent, the data body requested by the current client is sent to the back-end interface by default. The default proxy data is recommended. Of course, if there are special circumstances, it is ok to configure your own data.
In addition to the this.proxy parameter, you can also write the configuration for multiple concurrent requests:
this.proxy({ testInfo: { uri: 'github:other/info? test=test', form: { // formData }, headers: {}, // ... OtherInfo: {// same as above custom configuration}})
Copy the code
This gives you the flexibility to customize configurations at the interface level.
There are a lot of interesting details about the this.proxy method, which are recommended for interested students: github.com/xiongwilee/…
2. File proxy
The file agent can use the this.fetch method in the controller:
this.fetch(string)Copy the code
File request proxy is also very simple. For example, if you need to proxy an image request from Github and return it to the browser, see feclub.cn/user/avatar… Or to use the function to export files:
exports.avatar = async function() {await this.fetch(imgUrl);
}Copy the code
The important thing to note here is that response is terminated directly after this.fetch and is not executed on any other middleware.
The default template engine is swig, but the author has stopped maintaining swig. You can configure the desired template engine in config/main.*.js:
//Template Engine Configuration
template: 'nunjucks'Copy the code
You can also configure different template engines for different modules:
template: {
blog:'ejs'
}Copy the code
A list of supported template-engines is shown here: consolidate. Js# supported-template-engines
Call the this.render method in the controller to render the template engine:
exports.home = await function () {
await this.render('dashboard/site_home',{
breads : ['Site management'.'general'],
userInfo: this.userInfo,
siteInfo: this.siteInfo})}Copy the code
The template file is in the /views directory of the module path.
Note that Gracejs renders templates with the constant configuration in main.*.js handed to template data by default; This way, if you want to get a common configuration on the page (for example, the address of the CDN), you can get it in the constant sub of the template data.
Static — Static file service
The use of a static file is very simple, will/static / / or / * / static / * * * static file request agent to the module/static directory under the path:
//Configure static file routing
app.use(Middles.static(['/static/**/*'.'/*/static/**/*'], {
dir: config_path_project,
maxage: config_site.env = = 'production' && 60 * 60 * 1000
}));Copy the code
Take the static file of the blog in this example. The path of the static file under the blog project is: App/blog/static/image/bg. JPG, Access path as http://127.0.0.1/blog/static/image/bg.jpg or http://127.0.0.1/static/blog/image/bg.jpg
Two points to note:
- The static file port matches the current route port, so
/static/**/
or/*/static/*
Formal routing would be invalid; - In the production environment, it is recommended to use Nginx for static file services and purchase CDN to host static files.
MOCK functionality is actually quite simple to implement, and you can easily use MOCK data in a development environment.
Using the Demo module as an example, first add the proxy configuration to the main.development.js configuration file:
//Key/value pairs of various data prefixes and domain names requested in controller
api: {
//.
demo: 'http://${ip}:${port}/__MOCK__/demo/'
//.
}Copy the code
Next, add the mock folder to the demo module, and then add test.json:
File structure:
.├ ─ ─ controller ├ ─ ─ the mock|└── ├─ ├─ garbage ├─Copy the code
The contents of the file (which is what you want the request to return) :
You can also use comments in JSON file content:
/ *
* Interface to get user information
* /
{
code:0 //This is the code
}Copy the code
You can then verify that the test.json data has been returned by opening a browser to http://${ip}:${port}/__MOCK__/demo/test.
Finally, you can get mock data in your Controller business code using the proxy method:
this.proxy({
test:'demo:test'
})Copy the code
Note:
- If your mock file path is /mock/test/subtest.json then the proxy path is: test/subtest;
- It is strongly recommended that you unify the mock file to the real back-end request path so that you can mock the real path;
Consider this: an example of the mock feature in Gracejs
Secure — Security module
Considering that user routing is hosted entirely by Nodejs, CSRF issues have to be protected at the Nodejs layer as well. I have written an article before: CSRF defense mechanism in front and back end separation architecture. Here I will only write the use method, not detail the principle.
In Gracejs you can configure:
//CSRF configuration
csrf: {
//Name of the module for which XSRF protection is to be performed
module:[]}Copy the code
Then, in the business code, get the cookie named: grace_token and send it back with either POST or GET parameters. Of course, if you don’t want to pollute the parameter objects in Ajax, you can also store the cookie value in the X-Grace-Token header.
Gracejs listens for POST requests and returns an error if token validation fails.
Note: Database functionality is not recommended in a production environment
Using mongoDB in Gracejs is very simple, and of course without any pressure testing, there can be performance issues.
1. Connect to the database
In the config file config/main.*.js:
//Mongo configuration
mongo: {
options:{
//Mongoose configuration
},
api:{
'blog': 'mongodb://localhost:27017/blog'}},Copy the code
Mongo. options configudes the mongo connection pool and other information, and mongo. API configudes the database connection path corresponding to the site.
It is worth noting that once the database is configured, mongoose starts the connection as soon as Gracejs Server starts until Gracejs Server shuts down
Again using the example blog, see the app/blog/model/ Mongo directory:
└ ─ ─ mongo ├ ─ ─ Category. Js ├ ─ ─ the Link. The js ├ ─ ─ Post. Js └ ─ ─ User. JsCopy the code
A js file is a database table is related to configuration, in app/blog/model/mongo/Category. Js:
'use strict';
//The model name, which is the table name
let model = 'Category';
//Table structure
let schema = [{
id: {type: String,unique: true,required: true},
name: {type: String,required: true},
numb: {type: Number.'default':0}
}, {
autoIndex: true,
versionKey: false
}];
//The static method: http://mongoosejs.com/docs/guide.html#statics
let statics = {}
//Method of http://mongoosejs.com/docs/guide.html#methods
let methods = {
/ * *
* Get a list of blog categories
* /
list: function* () {
return this.model('Category').find();
}
}
module.exports.model = model;
module.exports.schema = schema;
module.exports.statics = statics;
module.exports.methods = methods;Copy the code
There are four main parameters:
model
, the name of the table, preferably with the same name as the current fileschema
, namely the mongoose schemamethods
, the schema extension method,It is recommended that all database meta-operations be defined in this objectstatics
, that is, static operation methods
3. Call the database in the controller
It is very simple to use in the controller, mainly through the this.mongo,this.mongoMap two methods.
Call mongoose Entity object to perform database CURD operation
Parameter Description:
@param [string] name: set Schema name in app/blog/model/mongo
Returns:
@return [object] A Mongoose Entity object after instantiating the Schema. You can invoke the methods of the object to perform database operations
case
Refer to the above Category. Js configuration to/controller/dashboard/app/blog post, js, for example, if you want to get in the blog list page blog classification data:
// http://127.0.0.1/dashboard/post/list
exports.list = async function() {let cates = await this.mongo('Category').list(a);this.body = cates;
}Copy the code
2)this.mongoMap(option)
Parallel multiple database operations
Parameters that
@param [array] option
@param [Object] option[]. Model Mongoose Entity Object, obtained by this.mongo(model)
@param [function] option[]. Fun Mongoose Entity method
@param [array] option[]. Arg Mongoose Entity Object method parameter
return
@return [array] Database operation result, which is returned in the form of the corresponding array
case
let PostModel = this.mongo('Post');
let mongoResult = await this.mongoMap([{
model: PostModel,
fun: PostModel.page,
arg: [pageNum]
},{
model: PostModel,
fun:PostModel.count,
arg: [pageNum]
}]);
let posts = mongoResult[0];//Gets the result of the first query, postModel.page
let page = mongoResult[1]; //Gets the result of the second query, postModel.count, executed concurrentlyCopy the code
Note: File upload and download is not recommended in the production environment
Like the database function, the file upload and download function is very simple to use, but is not recommended in the production environment. Because database functionality is currently supported only on a single server, service on multiple machines is problematic.
If you need to use the upload and download function online, you can pipe to the back-end interface using a proxy, or upload files directly to the back-end interface through the upload component.
1. File upload
Methods:
this.upload([opt])Copy the code
Example:
exports.aj_upload = async function() {
await this.bindDefault(a);let files = await this.upload(a);let res = {};
if (!files || files.length < 1) {
res.code = 1;
res.message = 'Failed to upload file!';
return this.body = res;
}
res.code = 0;
res.message = '';
res.data = {
files: files
}
return this.body = res;
}Copy the code
Methods:
this.download(filename, [opt])Copy the code
Example:
exports.download = async function() {
await this.download(this.query.file);
}Copy the code
other
Several core middleware in Gracejs are introduced. In addition, there are several middleware without detailed introduction, understanding can be:
- Gzip implementation: Use gzip to compress the body in response;
- HTTP Body content parsingParse the body of the request and save it to
this.request.body
In the field. - Simple session implementation: Saving sessions in memory or Redis is not recommended in production environments. Session services in the production environment are performed by the back end.
Finally, the operation and maintenance deployment of Gracejs is not detailed here. Pm2 is recommended, and there is no need to worry about service unavailability during server restart.
At this point, the whole front and back end of the service setup is introduced.
Before I explain how to build the front end with Gracejs, how does this “more thorough” separation of the front and back ends differ from single-page applications based on the MVVM framework?
Personally, I think there are the following points:
- More flexible o&M deployment Based on Nodejs Server server, server deployment can be independent from back-end machines. And the back-end students only need to focus on the implementation of the interface.
- The front-end technology stack is more unified, for example: PHP deploypage routing, the front-end is realized by MVVM framework, and the front-end also needs to learn PHP syntax to realize back-end routing.
- For example, you can easily build BigPipe using a template engine. You can also asynchronously and concurrently fetch the first screen data from the Intranet.
Of course Gracejs is just a server framework, the front-end architecture is up to you.
Boilerplate
There are already boilerplates based on Vue and RequireJS.
-
gulp-requirejs-boilerplate Requirejs supported.(by@xiongwilee)
-
grace-vue-webpack-boilerplate Both [email protected] & [email protected] supported.(by@thunf)
-
grace-vue2-webpack-boilerplate [email protected]. (by@haoranw)
Take a VUe-based build as an example.
The directory structure
A complete directory structure based on vue+Gracejs recommends using this pattern:
.├ ─ ─ app │ └ ─ ─ demo │ ├ ─ ─ build │ ├ ─ ─ controller │ ├ ─ ─ the mock │ ├ ─ ─ the static │ ├ ─ ─ views │ └ ─ ─ vues └ ─ ─ server ├ ─ ─ app │ └ ─ ─ Demo Exercises ── Middleware Exercises...Copy the code
Of course, Server (i.e. Gracejs) allows you to configure the app directory path, which you can place in any directory you want.
The demo module has two more directories than the demo module under the default server: Build and vues.
The build directory is a compilation script based on WebPack, and the vues directory is the front-end business file for all. Vue.
Webpack compiles vue files on vues and generates them to server, app, demo, or static. Other controller files that do not need to be compiled can be directly copied to the corresponding directory of server/app/demo/ by using the copy plug-in of Webpack.
Grace – vue-webpack-Boilerplate build implementation Of course, some knowledge of WebPack and VUE is required.
Students are welcome to contribute React and Angular boilerplate. After notifying us in the form of email or ISSUE, add it to Gracejs official document.
conclusion
Since then, Gracejs has finally been introduced. If you are interested, go to Github and give a star: github.com/xiongwilee/… .
Finally, welcome to issue, fork; If you have any questions, please contact xiongwilee[at]foxmail.com.