UNPKG

11.9 kBJavaScriptView Raw
1/**
2 * @file route manager
3 * @author panyuqi
4 * @desc generate route.js, multi entries in .lavas directory
5 */
6
7'use strict';
8
9Object.defineProperty(exports, "__esModule", {
10 value: true
11});
12
13var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
14
15var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
16
17var _stringify = require('babel-runtime/core-js/json/stringify');
18
19var _stringify2 = _interopRequireDefault(_stringify);
20
21var _assign = require('babel-runtime/core-js/object/assign');
22
23var _assign2 = _interopRequireDefault(_assign);
24
25var _set = require('babel-runtime/core-js/set');
26
27var _set2 = _interopRequireDefault(_set);
28
29var _fsExtra = require('fs-extra');
30
31var _path = require('path');
32
33var _crypto = require('crypto');
34
35var _serializeJavascript = require('serialize-javascript');
36
37var _serializeJavascript2 = _interopRequireDefault(_serializeJavascript);
38
39var _lodash = require('lodash.template');
40
41var _lodash2 = _interopRequireDefault(_lodash);
42
43var _router = require('./utils/router');
44
45var _webpack = require('./utils/webpack');
46
47var _path2 = require('./utils/path');
48
49function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
50
51const routerTemplate = (0, _path.join)(__dirname, './templates/router.tmpl');
52
53class RouteManager {
54
55 constructor(config = {}, env) {
56 this.config = config;
57 this.isDev = env === 'development';
58
59 if (this.config.globals && this.config.globals.rootDir) {
60 this.lavasDir = (0, _path.join)(this.config.globals.rootDir, './.lavas');
61 }
62
63 this.routes = [];
64
65 this.flatRoutes = new _set2.default();
66
67 this.errorRoute;
68 }
69
70 /**
71 * rewrite route path with rules
72 *
73 * @param {Array} rewriteRules rewrite rules
74 * @param {string} path original route path
75 * @return {string} path rewrited
76 */
77 rewriteRoutePath(rewriteRules, path) {
78 for (let i = 0; i < rewriteRules.length; i++) {
79 let rule = rewriteRules[i];
80 let { from, to } = rule;
81 /**
82 * if type of 'from' is regexp, use String.replace
83 */
84 if (from instanceof RegExp && from.test(path)) {
85 return path.replace(from, to);
86 }
87 /**
88 * if type of 'from' is array|string, 'to' must be a
89 * single rule, just replace with it
90 */
91 else if (Array.isArray(from) && from.includes(path) || typeof from === 'string' && from === path) {
92 return to;
93 }
94 }
95 return path;
96 }
97
98 /**
99 * merge routes with config recursively
100 *
101 * @param {Array} routes routes
102 * @param {Array} routesConfig config
103 * @param {Array} rewriteRules rewriteRules
104 * @param {Array} parentPath parentPath
105 */
106 mergeWithConfig(routes, routesConfig = [], rewriteRules = [], parentPath = '') {
107 /**
108 * In dev mode, we need to add timestamp to every route's hash as prefix.
109 * otherwise when we change the code in page.vue, route's hash remains the same,
110 * webpack hot middleware will throw a "Duplicate declaration" error.
111 */
112 let timestamp = this.isDev ? new Date().getTime() : '';
113 let errorIndex;
114
115 routes.forEach((route, index) => {
116 // add to set
117 this.flatRoutes.add(route);
118
119 // rewrite route path with rules
120 route.rewritePath = this.rewriteRoutePath(rewriteRules, route.path);
121 route.fullPath = parentPath ? `${parentPath}/${route.path}` : route.path;
122
123 // find error route
124 if (route.fullPath === this.config.errorHandler.errorPath) {
125 this.errorRoute = route;
126 // add default error route alias
127 this.errorRoute.alias = '*';
128 errorIndex = index;
129 }
130
131 // find route in config
132 let routeConfigArr = routesConfig.filter(function ({ pattern }) {
133 return pattern instanceof RegExp ? pattern.test(route.fullPath) : pattern === route.fullPath;
134 });
135
136 // mixin with config, rewrites path, add lazyLoading, meta
137 let routeConfig;
138 if (routeConfigArr.length !== 0) {
139 if (routeConfigArr.length === 1) {
140 routeConfig = routeConfigArr[0];
141 } else {
142 routeConfig = {};
143 routeConfigArr.forEach(tmpRouteConfig => (0, _assign2.default)(routeConfig, tmpRouteConfig));
144 }
145 }
146
147 if (routeConfig) {
148 let {
149 path: routePath,
150 lazyLoading,
151 chunkname
152 } = routeConfig;
153
154 (0, _assign2.default)(route, routeConfig, {
155 rewritePath: routePath || route.rewritePath,
156 lazyLoading: lazyLoading || !!chunkname
157 });
158 }
159
160 /**
161 * generate hash for each route which will be used in routes.js template,
162 * an underscore "_" will be added in front of each hash, because JS variables can't
163 * start with numbers
164 */
165 route.hash = timestamp + (0, _crypto.createHash)('md5').update(route.component).digest('hex');
166
167 /**
168 * turn route fullPath into regexp
169 * eg. /detail/:id => /^\/detail\/[^\/]+\/?$/
170 */
171 route.pathRegExp = route.rewritePath === '*' ? /^.*$/ : (0, _router.routes2Reg)(route.rewritePath);
172
173 // merge recursively
174 if (route.children && route.children.length) {
175 this.mergeWithConfig(route.children, routesConfig, rewriteRules, route.fullPath);
176 }
177 });
178
179 // remove errorRoute and add it to the end
180 if (errorIndex !== undefined) {
181 routes.splice(errorIndex, 1);
182 }
183 }
184
185 /**
186 * generate routes content which will be injected into routes.js
187 * based on nested routes
188 *
189 * @param {Array} routes route list
190 * @return {string} content
191 */
192 generateRoutesContent(routes) {
193 const generate = routes => routes.map(cur => {
194 // Call `this.$router.replace({name: xxx})` when path of 'xxx' contains '*' will throw error
195 // see https://github.com/vuejs/vue-router/issues/724
196 // Solution: write a normal path and add alias with '*'
197 let route = {
198 path: cur.rewritePath,
199 component: `_${cur.hash}`,
200 meta: cur.meta || {}
201 };
202
203 if (cur.name) {
204 route.name = cur.name;
205 }
206
207 if (cur.alias) {
208 route.alias = cur.alias;
209 }
210
211 if (cur.redirect) {
212 route.redirect = cur.redirect;
213 }
214
215 if (cur.children) {
216 route.children = generate(cur.children);
217 }
218
219 return route;
220 });
221
222 return (0, _stringify2.default)(generate(routes), undefined, 4).replace(/"component": "(_.+)"/mg, '"component": $1');
223 }
224
225 processRouterConfig(routerConfig) {
226 let {
227 mode = 'history',
228 base = '/',
229 pageTransition = { enable: false },
230 scrollBehavior
231 } = routerConfig;
232 // set page transition, support 2 types: slide|fade
233 let transitionType = pageTransition.type;
234 if (transitionType === 'slide') {
235 pageTransition = (0, _assign2.default)({
236 enable: true,
237 slideLeftClass: 'slide-left',
238 slideRightClass: 'slide-right',
239 alwaysBackPages: ['index'],
240 alwaysForwardPages: []
241 }, pageTransition);
242 } else if (transitionType) {
243 pageTransition = (0, _assign2.default)({
244 enable: true,
245 transitionClass: transitionType
246 }, pageTransition);
247 } else {
248 console.log('[Lavas] Page transition disabled');
249 pageTransition = { enable: false };
250 }
251
252 // scrollBehavior
253 if (scrollBehavior) {
254 scrollBehavior = (0, _serializeJavascript2.default)(scrollBehavior).replace('scrollBehavior(', 'function(');
255 }
256
257 return { mode, base, pageTransition, scrollBehavior };
258 }
259
260 /**
261 * write routes.js
262 *
263 */
264 writeRoutesSourceFile() {
265 var _this = this;
266
267 return (0, _asyncToGenerator3.default)(function* () {
268 let writeFile = _this.isDev ? _webpack.writeFileInDev : _fsExtra.outputFile;
269 let { mode, base, pageTransition, scrollBehavior } = _this.processRouterConfig(_this.config.router);
270 // add errorRoute to the end
271 _this.routes.push(_this.errorRoute);
272 let routesFilePath = (0, _path.join)(_this.lavasDir, 'router.js');
273 let routesContent = _this.generateRoutesContent(_this.routes);
274
275 let routesFileContent = (0, _lodash2.default)((yield (0, _fsExtra.readFile)(routerTemplate, 'utf8')))({
276 router: {
277 mode,
278 base,
279 routes: _this.flatRoutes,
280 scrollBehavior,
281 pageTransition
282 },
283 routesContent
284 });
285 yield writeFile(routesFilePath, routesFileContent);
286 })();
287 }
288
289 writeRoutesJsonFile() {
290 var _this2 = this;
291
292 return (0, _asyncToGenerator3.default)(function* () {
293 let generateRoutesJson = function (route) {
294 let tmpRoute = {
295 path: route.rewritePath,
296 name: route.name,
297 meta: route.meta || {}
298 };
299
300 if (route.alias) {
301 tmpRoute.alias = route.alias;
302 }
303
304 if (route.children) {
305 tmpRoute.children = [];
306 route.children.forEach(function (child) {
307 return tmpRoute.children.push(generateRoutesJson(child));
308 });
309 }
310
311 return tmpRoute;
312 };
313
314 let routerConfig = _this2.config.router;
315 let routesJson = {
316 ssr: routerConfig.ssr,
317 mode: routerConfig.mode,
318 base: routerConfig.base,
319 routes: []
320 };
321
322 _this2.routes.forEach(function (route) {
323 return routesJson.routes.push(generateRoutesJson(route));
324 });
325
326 yield (0, _fsExtra.outputFile)((0, _path2.distLavasPath)(_this2.config.build.path, 'routes.json'), (0, _stringify2.default)(routesJson, null, 4));
327 })();
328 }
329
330 /**
331 * output routes.js into .lavas according to /pages
332 *
333 */
334 buildRoutes() {
335 var _this3 = this;
336
337 return (0, _asyncToGenerator3.default)(function* () {
338 const { routes: routesConfig = [], rewrite: rewriteRules = [], pathRule } = _this3.config.router;
339 _this3.flatRoutes = new _set2.default();
340
341 // generate routes according to pages dir
342 _this3.routes = yield (0, _router.generateRoutes)((0, _path.join)(_this3.lavasDir, '../pages'), {
343 routerOption: { pathRule }
344 });
345
346 // merge with routes' config
347 _this3.mergeWithConfig(_this3.routes, routesConfig, rewriteRules);
348
349 // write route.js
350 yield _this3.writeRoutesSourceFile();
351
352 if (!_this3.isDev) {
353 // write routes.json
354 yield _this3.writeRoutesJsonFile();
355 }
356 })();
357 }
358}
359exports.default = RouteManager;
360module.exports = exports['default'];
\No newline at end of file