Home Reference Source Test

source/Component.js

  1. import 'dom-renderer/dist/polyfill';
  2.  
  3. import { stringifyDOM } from 'dom-renderer';
  4.  
  5. import {readFile, readdir, statSync, outputFile, remove} from 'fs-extra';
  6.  
  7. import {join, basename, dirname, extname} from 'path';
  8.  
  9. import Package from 'amd-bundle';
  10.  
  11. import * as Parser from './parser';
  12.  
  13. import { folderOf, metaOf } from './utility';
  14.  
  15. import { toDataURI } from '@tech_query/node-toolkit';
  16.  
  17.  
  18. /**
  19. * Component packer
  20. */
  21. export default class Component {
  22. /**
  23. * @param {string} path - Component directory
  24. */
  25. constructor(path) {
  26.  
  27. this.path = path;
  28.  
  29. this.name = basename( path );
  30.  
  31. this.entry = join(path, 'index');
  32. }
  33.  
  34. /**
  35. * @param {string} path
  36. *
  37. * @return {DocumentFragment}
  38. */
  39. static async parseHTML(path) {
  40.  
  41. const box = document.createElement('div'),
  42. fragment = document.createDocumentFragment();
  43.  
  44. box.innerHTML = (await readFile( path )) + '';
  45.  
  46. fragment.append(... box.childNodes);
  47.  
  48. return fragment;
  49. }
  50.  
  51. /**
  52. * @param {string} source - File path or Style source code
  53. * @param {?string} type - MIME type
  54. * @param {string} [base] - Path of the file which `@import` located in
  55. *
  56. * @return {?Element} Style element
  57. */
  58. static async parseCSS(source, type, base) {
  59.  
  60. var style;
  61.  
  62. type = type ? type.split('/')[1] : extname( source ).slice(1);
  63.  
  64. if (! source.includes('\n'))
  65. source = await readFile(base = source) + '';
  66.  
  67. style = Parser[type] && await Parser[type](source, dirname( base ));
  68.  
  69. return style && Object.assign(
  70. document.createElement('style'), {textContent: style}
  71. );
  72. }
  73.  
  74. /**
  75. * @param {DocumentFragment} fragment
  76. *
  77. * @return {Element[]}
  78. */
  79. static findStyle(fragment) {
  80.  
  81. return [ ].concat.apply([ ], Array.from(
  82. fragment.querySelectorAll('link[rel="stylesheet"], template'),
  83. tag => tag.content ?
  84. Array.from( tag.content.querySelectorAll('style') ) : tag
  85. ));
  86. }
  87.  
  88. /**
  89. * @param {string} tagName
  90. *
  91. * @return {string}
  92. */
  93. static identifierOf(tagName) {
  94.  
  95. return tagName[0].toUpperCase() +
  96. tagName.replace(/-(\w)/g, (_, char) => char.toUpperCase()).slice(1);
  97. }
  98.  
  99. /**
  100. * @param {String} path - Full name of a JS file
  101. *
  102. * @return {String} Packed JS source code
  103. */
  104. static packJS(path) {
  105.  
  106. const single_entry = join(folderOf().lib || '', 'index.js');
  107.  
  108. const name = (path === single_entry) && metaOf().name;
  109.  
  110. path = path.split('.').slice(0, -1).join('.');
  111.  
  112. return (new Package( path )).bundle(
  113. name || basename( dirname( path ) )
  114. );
  115. }
  116.  
  117. /**
  118. * @return {DocumentFragment} HTML version bundle of this component
  119. */
  120. async toHTML() {
  121.  
  122. const fragment = await Component.parseHTML(this.entry + '.html'),
  123. CSS = [ ];
  124.  
  125. for (let sheet of Component.findStyle( fragment )) {
  126.  
  127. let style = await Component.parseCSS(
  128. (sheet.tagName === 'STYLE') ?
  129. sheet.textContent : join(this.path, sheet.getAttribute('href')),
  130. sheet.type,
  131. this.entry + '.css'
  132. );
  133.  
  134. if (! style) continue;
  135.  
  136. sheet.replaceWith( style );
  137.  
  138. if (style.parentNode === fragment) CSS.push( style );
  139. }
  140.  
  141. fragment.querySelector('template').content.prepend(... CSS);
  142.  
  143. const script = fragment.querySelector('script');
  144.  
  145. if ( script )
  146. script.replaceWith(Object.assign(document.createElement('script'), {
  147. text: `\n${
  148. Component.packJS( join(this.path, script.getAttribute('src')) )
  149. }\n`
  150. }));
  151.  
  152. return fragment;
  153. }
  154.  
  155. /**
  156. * @protected
  157. *
  158. * @param {String} file - File path
  159. *
  160. * @return {String} Legal ECMAScript source code
  161. */
  162. async assetOf(file) {
  163.  
  164. switch ( extname( file ).slice(1) ) {
  165. case 'html':
  166. case 'htm':
  167. file = stringifyDOM(await this.toHTML()); break;
  168. case 'css':
  169. case 'less':
  170. case 'sass':
  171. case 'scss':
  172. case 'stylus':
  173. file = (await Component.parseCSS( file )).textContent; break;
  174. case 'js':
  175. return;
  176. case 'json':
  177. return (await readFile( file )) + '';
  178. case 'yaml':
  179. case 'yml':
  180. return Parser.yaml((await readFile( file )) + '');
  181. default:
  182. file = toDataURI( file );
  183. }
  184.  
  185. return JSON.stringify( file );
  186. }
  187.  
  188. /**
  189. * @return {string} JS version bundle of this component
  190. */
  191. async toJS() {
  192.  
  193. const temp_file = [ ];
  194.  
  195. for (let file of await readdir( this.path )) {
  196.  
  197. file = join(this.path, file);
  198.  
  199. if (! statSync( file ).isFile()) continue;
  200.  
  201. let temp = `${file}.js`;
  202.  
  203. file = await this.assetOf( file );
  204.  
  205. if (!(file != null)) continue;
  206.  
  207. temp_file.push( temp );
  208.  
  209. await outputFile(temp, `export default ${file}`);
  210. }
  211.  
  212. const source = Component.packJS(this.entry + '.js');
  213.  
  214. await Promise.all( temp_file.map(file => remove( file )) );
  215.  
  216. return source;
  217. }
  218. }