├── test ├── mocha.opts └── unit │ └── lib │ └── index.js ├── .gitignore ├── lib ├── read-data-from-files.js ├── plugins │ └── bootstrap │ │ ├── beforeAllComponents.js │ │ └── index.js ├── read-config-from-files.js └── index.js ├── .travis.yml ├── README.TODO.md ├── package.json └── README.md /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --timeout 10000 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | npm-debug.log 4 | .coveralls.yml 5 | -------------------------------------------------------------------------------- /lib/read-data-from-files.js: -------------------------------------------------------------------------------- 1 | function readDataFromFiles () { 2 | 3 | } 4 | 5 | module.exports = readDataFromFiles 6 | -------------------------------------------------------------------------------- /lib/plugins/bootstrap/beforeAllComponents.js: -------------------------------------------------------------------------------- 1 | function beforeAllComponents () { 2 | 3 | } 4 | 5 | module.exports = beforeAllComponents 6 | -------------------------------------------------------------------------------- /lib/plugins/bootstrap/index.js: -------------------------------------------------------------------------------- 1 | const beforeAllComponents = require('./beforeAllComponents') 2 | module.exports = { beforeAllComponents } 3 | -------------------------------------------------------------------------------- /test/unit/lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const htr = require('../../../lib/index') 3 | const assert = require('assert') 4 | 5 | describe('index.js', function () { 6 | it('should pass', function () { 7 | assert(typeof (htr) === 'function') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '7.1.0' 10 | before_install: 11 | - npm i -g npm@^3.10.3 12 | before_script: 13 | - npm prune 14 | after_success: 15 | - npm run semantic-release 16 | branches: 17 | except: 18 | - /^v\d+\.\d+\.\d+$/ 19 | -------------------------------------------------------------------------------- /README.TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - NEXT: 4 | - the bootstrap plugin to provide skeletion for other plugins 5 | 6 | - stage 1: convert files into react components 7 | - import all the config files, parse as config ( not part of plugin ) 8 | - for each hrc/pages/[PageName].json, we generate components in destination 9 | - preprocess file and add data-component-name in it, (N * M) 10 | N is number of components, M is number of nodes 11 | we can implement one pass solution to boost performance to (M) 12 | pick components using these tags using one traverse (M) 13 | - we can save temp files and use dif to boost performance 14 | - we can let user mark component as local boost performance 15 | - provide hooks in different levels so that we have a plugin systems 16 | - each node 17 | - each file and component, they may be the same or file contains component 18 | - plugins: 19 | - className 20 | - style 21 | - props (later) 22 | 23 | - stage 2: add container components and resolve dependencies 24 | - global components for all pages? config.options.globalComponents? 25 | 26 | - stage 3: change urls for react router 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-to-react-converter", 3 | "description": "Convert html files to a react app", 4 | "main": "lib/index.js", 5 | "scripts": { 6 | "fix": "standard --fix", 7 | "lint": "standard", 8 | "test": "standard && istanbul cover _mocha", 9 | "test:dev": "npm run test && open coverage/lcov-report/index.html", 10 | "watch": "mocha --watch", 11 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/poetic/html-to-react-converter.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "html", 20 | "converter" 21 | ], 22 | "author": "Chun-Yang ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/poetic/html-to-react-converter/issues" 26 | }, 27 | "homepage": "https://github.com/poetic/html-to-react-converter#readme", 28 | "devDependencies": { 29 | "istanbul": "^0.4.5", 30 | "mocha": "^3.1.2", 31 | "semantic-release": "^4.3.5", 32 | "standard": "^8.5.0" 33 | }, 34 | "dependencies": { 35 | "babel-traverse": "^6.18.0", 36 | "naive-logger": "^1.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/read-config-from-files.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { fatal } = require('naive-logger') 3 | 4 | function readFile (filePath, msg) { 5 | let fileContent 6 | 7 | try { 8 | fileContent = fs.readFileSync(filePath) 9 | } catch (e) { 10 | if (msg) { 11 | fatal(msg) 12 | } else { 13 | throw e 14 | } 15 | } 16 | 17 | return JSON.parse(fileContent) 18 | } 19 | 20 | function readdir (dirPath, msg) { 21 | let pageFiles = [] 22 | 23 | try { 24 | pageFiles = fs.readdirSync(dirPath) 25 | } catch (e) { 26 | // do nothing 27 | } 28 | 29 | if (!pageFiles.length) { 30 | fatal(msg) 31 | } 32 | 33 | return pageFiles 34 | } 35 | 36 | module.exports = function readConfigFromFiles () { 37 | const config = {} 38 | 39 | config.options = readFile( 40 | 'hrc/options.json', 41 | 'You MUST create hrc/options.json, please read: https://github.com/poetic/html-to-react-converter#hrcconfigjson' 42 | ) 43 | 44 | config.routes = readFile( 45 | 'hrc/routes.json', 46 | 'You MUST create hrc/routes.json, please read: https://github.com/poetic/html-to-react-converter#hrcroutesjson' 47 | ) 48 | 49 | config.pages = readdir( 50 | 'hrc/pages/', 51 | 'You MUST create at least one hrc/pages/[PageName].json, please read: https://github.com/poetic/html-to-react-converter#hrcpagespagenamejson' 52 | ).map((name) => readFile(`hrc/pages/${name}`)) 53 | 54 | return config 55 | } 56 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const traverse = require('babel-traverse').default 2 | const readConfigFromFiles = require('./read-config-from-files') 3 | const readDataFromFiles = require('./read-data-from-files') 4 | const bootstrap = require('./plugins/bootstrap') 5 | const defaultPlugins = [bootstrap] 6 | 7 | function applyPluginHooks ({plugins, hookName, arg}) { 8 | return plugins.reduce((acc, plugin) => { 9 | if (plugin[hookName]) { 10 | return plugin[hookName](acc) 11 | } else { 12 | return acc 13 | } 14 | }, arg) 15 | } 16 | 17 | function index (props) { 18 | const { configIsFromFiles, dataIsFromFiles } = props 19 | const config = configIsFromFiles ? readConfigFromFiles() : props.config 20 | const data = dataIsFromFiles ? readDataFromFiles() : props.data 21 | 22 | const customPlugins = config.options.plugins.map((plugin) => require(plugin)) 23 | const plugins = defaultPlugins.concat(customPlugins) 24 | 25 | let arg = { config, data, pluginData: {}, components: [], indexFile: null } 26 | 27 | arg = applyPluginHooks({ plugins, hookName: 'beforeAllComponents', arg }) 28 | 29 | for (let i = 0; i < arg.components.length; i++) { 30 | arg.component = arg.components[i] 31 | arg = applyPluginHooks({ plugins, hookName: 'beforeEachComponent', arg }) 32 | 33 | traverse(arg.component.ast, { 34 | JSXElement (nodePath) { 35 | arg.nodePath = nodePath 36 | arg = applyPluginHooks({ plugins, hookName: 'eachNodePath', arg }) 37 | delete arg.nodePath 38 | } 39 | }) 40 | 41 | arg = applyPluginHooks({ plugins, hookName: 'afterEachComponent', arg }) 42 | delete arg.component 43 | } 44 | 45 | arg = applyPluginHooks({ plugins, hookName: 'afterAllComponents', arg }) 46 | 47 | return arg 48 | } 49 | 50 | module.exports = index 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html-to-react-converter 2 | 3 | html-to-react-converter converts htmls to a working and modifiable react app 4 | using several configuration files. 5 | 6 | ## Usage 7 | 8 | You need to create config files for the `htr` cli to extract react components 9 | and generate other files from html files. 10 | 11 | ### hrc/options.json 12 | 13 | - source: 14 | - description: The source file or folder for the html files, relative to the project folder. 15 | - default: './' 16 | - destination: 17 | - description: The destination folder for all the components 18 | - default: 'client/' 19 | - plugins 20 | - plugins.[]: 21 | - description: Custom plugins as node modules you want to use 22 | 23 | - Example: 24 | ``` 25 | { 26 | "source": ".design/html", 27 | "destination": "client/", 28 | "plugins": [] 29 | } 30 | ``` 31 | 32 | ### hrc/routes.json 33 | 34 | This file is used to create react Route file so that the generate files are 35 | a complete react project. 36 | 37 | ### hrc/pages/[PageName].json 38 | 39 | Theses files are used to extract and create react components from htmls. 40 | We use jquery selector to target the components. 41 | 42 | - file: 43 | - description: file path relative to the source configured in options.json 44 | - default: path/to/source/[PageName].html 45 | - components 46 | - components.[].selector: 47 | - description: the selector used to target the html 48 | - required 49 | - components.[].name: 50 | - description: the name of the generated component 51 | - required 52 | - components.[].skip: 53 | - description: if skip is true, we do not write to the destination folder 54 | - components.[].props 55 | - components.[].props.[].key: 56 | - description: the key of the prop we apply to the tag 57 | - required 58 | - components.[].props.[].value: 59 | - description: the value of the prop we apply to the tag 60 | - required 61 | - note: this a STRING, we will paste this string as literal code 62 | 63 | - Example: 64 | ``` 65 | { 66 | "file": "users.html", 67 | "components": [ 68 | { 69 | "selector": "#nav-bar", 70 | "name": "NavBar", 71 | "skip": true, 72 | "props": [ 73 | { 74 | "selector": ".picture", 75 | "key": "src", 76 | "value": "\"`images/${this.props.user.picture}`\"" 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | ``` 83 | 84 | ### CLI 85 | After you run `htr`, we will create the following contents: 86 | - path/to/destination/components/ 87 | 88 | This will contain all generated components. For each component, we will 89 | generate a container component that you can manipulate. 90 | 91 | - path/to/destination/index.jsx 92 | 93 | This will contain all the routes 94 | 95 | - htr/staging/ 96 | 97 | This will contain all staging files that when not skipped will be 98 | copied to the destination components 99 | 100 | ## [TODO](README.TODO.md) 101 | --------------------------------------------------------------------------------