1+ const path = require ( 'path' ) ;
2+ const toSource = require ( 'tosource' ) ;
3+ const deepCopy = require ( 'deepcopy' ) ;
4+ const loaderUtils = require ( "loader-utils" ) ;
5+
6+ class Loader {
7+ constructor ( loaderApi ) {
8+ this . loaderApi = loaderApi ;
9+
10+ const options = loaderUtils . getOptions ( this . loaderApi ) || { } ;
11+ this . componentsDir = options . componentsDir || '.' ;
12+ this . relativePath = options . relativePath || false ;
13+ this . inheritProps = options . inheritProps || { } ;
14+ }
15+
16+ transform ( source ) {
17+ let [ imports , routes ] = this . transformRoutes ( eval ( source ) ) ;
18+ return (
19+ `${ this . generateImports ( imports ) } `
20+ + `${ this . generateRoutes ( routes ) } `
21+ ) ;
22+ }
23+
24+ /**
25+ * Get name of component in the route
26+ *
27+ * @param {* } route route config object
28+ * @returns {string } component name
29+ */
30+ componentName ( route ) {
31+ // use component name specified in route
32+ if ( route . componentName ) {
33+ return route . componentName ;
34+ }
35+
36+ // default name equals base name of component path
37+ return path . basename ( route . component , path . extname ( route . component ) ) ;
38+ }
39+
40+ /**
41+ * Get path to import component from
42+ *
43+ * @param {* } route route config object
44+ * @returns {string } component path
45+ */
46+ componentPath ( route ) {
47+ if ( path . isAbsolute ( route . component ) || ! route . component . startsWith ( '.' ) ) {
48+ // load for absolute path or node_modules, no change
49+ return route . component . replace ( / \\ / g, '\\\\' ) ;
50+ }
51+ else {
52+ // relative path, join with base path
53+ // NB: prefix ./ to not load from node_modules
54+ return `./${ path . join ( this . componentsDir , route . component ) } ` . replace ( / \\ / g, '\\\\' ) ;
55+ }
56+ }
57+
58+ /**
59+ * Transform routes config into react router config by collecting component imports
60+ *
61+ * @param {Array } routes routes config
62+ * @returns { [ Array, Array ] } [ component path to import, react routes config ]
63+ */
64+ transformRoutes ( routes ) {
65+ let imports = new Set ( ) ;
66+ routes . forEach ( route => this . transformRouteIter ( route , imports , '' , this . inheritProps ) ) ;
67+
68+ return [ Array . from ( imports ) , routes ] ;
69+ }
70+
71+ transformRouteIter ( route , imports , cwd , inheritProps ) {
72+ // transform component configuration into name and import
73+ if ( route . component ) {
74+ imports . add ( `${ this . componentName ( route ) } |${ this . componentPath ( route ) } ` ) ;
75+ route . component = this . componentName ( route ) ;
76+ delete route . componentName ;
77+ }
78+
79+ // transform relative path to absolute
80+ if ( route . path && this . relativePath ) {
81+ cwd = route . path = path . posix . join ( cwd , route . path ) ;
82+ }
83+
84+ // merge inherit props to route
85+ if ( route . inheritProps ) {
86+ inheritProps = Object . assign ( inheritProps , route . inheritProps ) ;
87+ delete route . inheritProps ;
88+ }
89+ Object . assign ( route , inheritProps ) ;
90+
91+ // recursive transform child routes
92+ if ( route . routes ) {
93+ route . routes . forEach ( route => this . transformRouteIter ( route , imports , cwd , inheritProps ) ) ;
94+ }
95+ }
96+
97+ /**
98+ * Generate js import expression for components
99+ *
100+ * @param {string } imports import information for react components
101+ * @returns {string } js source code that import components
102+ */
103+ generateImports ( imports ) {
104+ return imports . map ( kv => {
105+ let [ name , file ] = kv . split ( '|' ) ;
106+ return `import ${ name } from '${ file } ';\n` ;
107+ } ) . join ( '' ) ;
108+ }
109+
110+ /**
111+ * Generate js export expression for react-router-config
112+ *
113+ * @param {* } routes react route configs
114+ * @returns {string } js source code that export react router config
115+ */
116+ generateRoutes ( routes ) {
117+ return `export default [${ routes . map ( route => this . generateRouteIter ( route , '' ) ) . join ( ',\n' ) } ];` ;
118+ }
119+
120+ generateRouteIter ( route , indent ) {
121+ let component = deepCopy ( route . component ) ;
122+ let routes = deepCopy ( route . routes ) ;
123+ delete route . component ;
124+ delete route . routes ;
125+
126+ let source = toSource ( route , null , ' ' , indent ) ;
127+ let nextIndent = indent + ' ' ;
128+
129+ return [
130+ source === '{}' ? '{' : `${ source . substring ( 0 , source . length - 2 ) } ,` ,
131+ component ? `${ nextIndent } component:${ component } ,` : '' ,
132+ routes ? `${ nextIndent } routes:[${ routes . map ( route => `${ this . generateRouteIter ( route , `${ nextIndent } ` ) } ` ) . join ( `,\n${ nextIndent } ` ) } ]` : '' ,
133+ `${ indent } }`
134+ ] . filter ( str => str ) . join ( '\n' ) ;
135+ }
136+ }
137+
138+ module . exports = exports = function ( source ) {
139+ let loader = new Loader ( this ) ;
140+ return loader . transform ( source ) ;
141+ } ;
142+
143+ exports . Loader = Loader ;
0 commit comments