├── .gitattributes ├── .remarkignore ├── template ├── template.js ├── tailwind.css └── index.template.html ├── bin └── cli.js ├── .gitignore ├── tailwind.config.js ├── test ├── fixtures │ ├── components │ │ └── BaseButton.vue │ ├── Header.vue │ └── App.vue └── test.js ├── .travis.yml ├── .editorconfig ├── src ├── index.js ├── plugins.js ├── cli.js ├── utils.js ├── compile-to-html.js └── compile-to-component.js ├── .lintstagedrc.js ├── rollup.config.js ├── LICENSE ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | test/snapshots/**/*.md 2 | -------------------------------------------------------------------------------- /template/template.js: -------------------------------------------------------------------------------- 1 | // This need to be empty!! 2 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/cli'); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | .idea 4 | node_modules 5 | coverage 6 | .nyc_output 7 | dist -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: {}, 3 | variants: {}, 4 | plugins: [] 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/components/BaseButton.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /template/tailwind.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | - 'lts/*' 5 | - 'node' 6 | script: 7 | npm run test-coverage 8 | after_success: 9 | npm run coverage 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import compileToHTML from './compile-to-html'; 2 | import compileToComponent from './compile-to-component'; 3 | 4 | export default { 5 | compileToHTML, 6 | compileToComponent 7 | }; 8 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*.md,!test/**/*.md": [ 3 | filenames => filenames.map(filename => `remark ${filename} -qfo`) 4 | ], 5 | 'package.json': 'fixpack', 6 | '*.js': 'xo --fix' 7 | }; 8 | -------------------------------------------------------------------------------- /template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/Header.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /test/fixtures/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import { brotliCompressSync } from 'zlib'; 3 | import { terser } from "rollup-plugin-terser"; 4 | import gzipPlugin from 'rollup-plugin-gzip'; 5 | 6 | export default { 7 | input: ['src/index.js', 'src/cli.js'], 8 | output: { 9 | dir: 'dist', 10 | format: 'cjs' 11 | }, 12 | plugins: [ 13 | resolve(), 14 | terser(), 15 | gzipPlugin({ 16 | customCompression: content => 17 | brotliCompressSync(Buffer.from(content)), fileName: '.br' 18 | }) 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const fs = require('fs-extra'); 3 | 4 | const { compileToHTML } = require('../dist'); 5 | 6 | test.beforeEach(t => { 7 | Object.assign(t.context, { props: 'Athif Humam' }); 8 | }); 9 | 10 | // Remove html_components folder after each test 11 | test.afterEach(async _ => { 12 | await fs.rmdir('html_components', async err => { 13 | if (err) { 14 | throw err; 15 | } 16 | }); 17 | }); 18 | 19 | test('can render html', async t => { 20 | const { App } = await compileToHTML('./fixtures/App.vue', { 21 | props: { name: t.context.props } 22 | }); 23 | const result = 24 | '
\n' + 25 | ' This is header, Athif Humam\n'; 26 | 27 | t.true(App.includes(result)); 28 | }); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Muhammad Athif Humam (https://athif23.github.io/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/plugins.js: -------------------------------------------------------------------------------- 1 | const pluginCommonJS = require('@rollup/plugin-commonjs'); 2 | const pluginVue = require('rollup-plugin-vue'); 3 | const path = require('path'); 4 | 5 | const purgeCSSOption = { 6 | content: ['./**/**/*.vue'], 7 | 8 | // This is the function used to extract class names from your templates 9 | defaultExtractor: content => { 10 | // Capture as liberally as possible, including things like `h-(screen-1.5)` 11 | const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []; 12 | 13 | // Capture classes within other delimiters like .block(class="w-1/2") in Pug 14 | const innerMatches = 15 | content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []; 16 | 17 | return broadMatches.concat(innerMatches); 18 | } 19 | }; 20 | 21 | function plugins({ tailwind, purge } = { tailwind: false, purge: false }) { 22 | const postcssPlugins = _ => { 23 | return [ 24 | require('postcss-import')(), 25 | require('postcss-url')(), 26 | tailwind && require('tailwindcss')( 27 | path.join(__dirname, '../tailwind.config.js') 28 | ), 29 | require('autoprefixer')(), 30 | purge && require('@fullhuman/postcss-purgecss')(purgeCSSOption), 31 | require('cssnano')({ 32 | preset: 'default' 33 | }) 34 | ].filter(Boolean); 35 | }; 36 | 37 | const options = { 38 | defaultLang: { markdown: 'pluginMarkdown' }, 39 | css: true, 40 | style: { 41 | postcssPlugins: postcssPlugins() 42 | } 43 | }; 44 | return [pluginCommonJS(), pluginVue(options)]; 45 | } 46 | 47 | export default plugins; 48 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | const start = Date.now(); 2 | 3 | const argv = require('minimist')(process.argv.slice(2)); 4 | const path = require('path'); 5 | const chalk = require('chalk'); 6 | const Fdir = require('fdir'); 7 | 8 | import { resolvePaths, getDirectories, getVueFiles, normalizeBoolean } from './utils'; 9 | 10 | import compileToHTML from './compile-to-html'; 11 | 12 | function showHelp() { 13 | console.log(` 14 | Usage: vue2html [command] [path] [--options] 15 | Commands: 16 | vue2html [path] Convert vue component to html by default. 17 | vue2html component [path] Convert vue component to js component. 18 | Options: 19 | --help, -h [boolean] show help 20 | --version, -v [boolean] show version 21 | --tailwind [boolean] use tailwind plugin 22 | --purge [boolean] use purgecss 23 | --config, -c [string] use specified config file 24 | --base [string] public base path (default: /) 25 | --outDir=[name] [string] output directory (default: html_components) 26 | --template, -t [string] use specified server-renderer template. 27 | `); 28 | } 29 | 30 | console.log(chalk.cyan(`vue2html v${require('../package.json').version}`)); 31 | (async () => { 32 | const { _: components, help, h, version, v, outDir, template, t, tailwind, purge } = argv; 33 | 34 | if (help || h) { 35 | showHelp(); 36 | return; 37 | } else if (version || v) { 38 | return; 39 | } 40 | 41 | // Get all vue file paths 42 | const paths = getVueFiles(components); 43 | // Get all directory paths 44 | const dirs = getDirectories(components); 45 | 46 | if (dirs.length > 1) { 47 | dirs.forEach(dir => { 48 | // Create the builder 49 | const api = new Fdir() 50 | .glob('./**/*.vue') 51 | .withBasePath() 52 | .crawl(path.join(dir)); 53 | 54 | // Get all files in a directory synchronously 55 | const files = api.sync(); 56 | 57 | paths.push(...resolvePaths(files)); 58 | }); 59 | } 60 | 61 | // Start spinner 62 | await compileToHTML(paths.length === 1 ? paths[0] : paths, { 63 | context: { 64 | title: 'Title' 65 | }, 66 | tailwind, 67 | purge, 68 | writeToFile: true, 69 | outDir, 70 | silent: false, 71 | template: template === undefined ? normalizeBoolean(t) : normalizeBoolean(template) 72 | }); 73 | 74 | console.log(`Finished compiled in ${(Date.now() - start) / 1000}ms.`); 75 | })(); 76 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | 4 | function isVueFile(path = '') { 5 | const re = /(.vue)+/; 6 | return re.test(path); 7 | } 8 | 9 | function getComponentName(path) { 10 | const re = /(\w+)(.vue+)/; 11 | return re.exec(path)[1]; 12 | } 13 | 14 | function parseStrToFunc(code) { 15 | /* eslint-disable no-new-func */ 16 | return new Function(code); 17 | /* eslint-enable no-new-func */ 18 | } 19 | 20 | function convertSlash(path) { 21 | const isExtendedLengthPath = /^\\\\\?\\/.test(path); 22 | const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex 23 | 24 | if (isExtendedLengthPath || hasNonAscii) { 25 | return path; 26 | } 27 | 28 | return path.replace(/\\/g, '/'); 29 | } 30 | 31 | function getVueFiles(paths) { 32 | paths = paths.filter(p => { 33 | return path.extname(p) === '.vue'; 34 | }); 35 | return resolvePaths(paths); 36 | } 37 | 38 | function getDirectories(paths) { 39 | const dirs = []; 40 | paths.forEach(p => { 41 | if (path.extname(p) !== '.vue') { 42 | dirs.push(p); 43 | } 44 | }); 45 | return resolvePaths(dirs); 46 | } 47 | 48 | function resolvePaths(paths) { 49 | paths = paths.map(p => { 50 | if (p[0] === '\\' || p[0] === '/') { 51 | p = p.slice(1); 52 | } 53 | 54 | return convertSlash(path.join(p)); 55 | }); 56 | return paths; 57 | } 58 | 59 | function isStringAndEmpty(val) { 60 | return typeof val === 'string' && val === ''; 61 | } 62 | 63 | function normalizeBoolean(bool = '') { 64 | const b = bool.toLowerCase ? bool.toLowerCase() : bool; 65 | switch (b) { 66 | case 'true': 67 | return true; 68 | case 'false': 69 | return false; 70 | case true: 71 | case false: 72 | return b; 73 | default: 74 | return b; 75 | } 76 | } 77 | 78 | async function copyFirstComponent(components = []) { 79 | if (components.length === 0) { 80 | return components; 81 | } 82 | 83 | const firstC = components.shift(); 84 | 85 | const firstCDir = path.dirname(firstC.resolvePath); 86 | const copyPath = path.join(firstCDir, '__copy__' + firstC.name + '.vue'); 87 | await fs.copyFile(firstC.resolvePath, copyPath, err => { 88 | if (err) { 89 | throw err; 90 | } 91 | }); 92 | 93 | firstC.path = convertSlash( 94 | path.join(path.dirname(firstC.path), '__copy__' + firstC.name + '.vue') 95 | ); 96 | firstC.resolvePath = copyPath; 97 | 98 | components.unshift(firstC); 99 | 100 | return [components, convertSlash(copyPath)]; 101 | } 102 | 103 | export { 104 | isVueFile, 105 | getComponentName, 106 | parseStrToFunc, 107 | convertSlash, 108 | resolvePaths, 109 | getDirectories, 110 | getVueFiles, 111 | isStringAndEmpty, 112 | normalizeBoolean, 113 | copyFirstComponent 114 | }; 115 | -------------------------------------------------------------------------------- /src/compile-to-html.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import compileToComponent from './compile-to-component'; 3 | import { isStringAndEmpty } from './utils'; 4 | 5 | const fs = require('fs-extra'); 6 | const path = require('path'); 7 | 8 | export default async function(filenames = '', options = {}) { 9 | if (!filenames) { 10 | return; 11 | } 12 | 13 | const componentHtmls = {}; 14 | let renderer; 15 | let childSpinner = { stop: () => {}, start: () => {}, succeed: () => {} }; 16 | 17 | // Extend default options 18 | const defaultOptions = { 19 | props: {}, 20 | context: {}, 21 | template: '', 22 | tailwind: false, 23 | purge: false, 24 | writeToFile: false, 25 | outDir: 'html_components', 26 | silent: true 27 | }; 28 | Object.assign(defaultOptions, options); 29 | 30 | if ( 31 | typeof defaultOptions.template !== 'boolean' || 32 | defaultOptions.template 33 | ) { 34 | const templatePath = 35 | isStringAndEmpty(defaultOptions.template) || 36 | defaultOptions.template === true 37 | ? path.resolve(__dirname, '../template', 'index.template.html') 38 | : defaultOptions.template; 39 | const template = await fs.readFile(templatePath, 'utf-8'); 40 | 41 | renderer = require('vue-server-renderer').createRenderer({ 42 | template, 43 | runInNewContext: true 44 | }); 45 | } else { 46 | renderer = require('vue-server-renderer').createRenderer(); 47 | } 48 | 49 | childSpinner = require('ora')('Compile to normalized component'); 50 | 51 | childSpinner.start(); 52 | const components = await compileToComponent(filenames, defaultOptions); 53 | childSpinner.stop(); 54 | 55 | // Make sure output directory exists 56 | const outDirname = defaultOptions.outDir || 'html_components'; 57 | await fs.ensureDir(outDirname); 58 | // Loop trough components and render them to html 59 | Object.keys(components).forEach((key, idx) => { 60 | childSpinner.start(`Compiling ${key} component to HTML...`); 61 | let component = components[key]; 62 | 63 | // This is needed because when filenames is Array 64 | // all the property needed are inside default property. 65 | if (component.default) { 66 | component = component.default; 67 | } 68 | 69 | const props = Array.isArray(filenames) 70 | ? defaultOptions.props[idx] 71 | : defaultOptions.props; 72 | 73 | const vueInstance = new Vue({ 74 | render: h => h(component, { props }) 75 | }); 76 | 77 | renderer.renderToString( 78 | vueInstance, 79 | defaultOptions.context, 80 | async (err, html) => { 81 | if (err) { 82 | throw err; 83 | } 84 | 85 | componentHtmls[key] = html; 86 | 87 | if (defaultOptions.writeToFile) { 88 | const filename = key + '.html'; 89 | await fs.writeFile(path.join(outDirname, filename), html); 90 | } 91 | } 92 | ); 93 | childSpinner.succeed(`Success compiling ${key} component`); 94 | }); 95 | 96 | return componentHtmls; 97 | } 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2html", 3 | "description": "A simple tool to convert vue component to html ", 4 | "version": "0.2.1", 5 | "author": "Muhammad Athif Humam (https://athif23.github.io/)", 6 | "bugs": { 7 | "url": "https://github.com/athif23/vue2html/issues", 8 | "email": "emailta.indo@gmail.com" 9 | }, 10 | "commitlint": { 11 | "extends": [ 12 | "@commitlint/config-conventional" 13 | ] 14 | }, 15 | "contributors": [ 16 | "Muhammad Athif Humam (https://athif23.github.io/)" 17 | ], 18 | "bin": { 19 | "vue2html": "bin/cli.js" 20 | }, 21 | "files": [ 22 | "dist/**/*.js", 23 | "bin", 24 | "template", 25 | "tailwind.config.js" 26 | ], 27 | "dependencies": { 28 | "@fullhuman/postcss-purgecss": "^2.2.0", 29 | "@rollup/plugin-alias": "^3.1.0", 30 | "@rollup/plugin-commonjs": "^12.0.0", 31 | "@rollup/plugin-node-resolve": "^8.0.0", 32 | "autoprefixer": "^9.8.0", 33 | "chalk": "^4.0.0", 34 | "cssnano": "^4.1.10", 35 | "fdir": "^3.4.2", 36 | "fs-extra": "^9.0.0", 37 | "minimist": "^1.2.5", 38 | "ora": "^4.0.4", 39 | "postcss": "^7.0.32", 40 | "postcss-assets": "^5.0.0", 41 | "postcss-import": "^12.0.1", 42 | "postcss-url": "^8.0.0", 43 | "replace-in-file": "^6.0.0", 44 | "rollup": "^2.12.0", 45 | "rollup-plugin-gzip": "^2.5.0", 46 | "rollup-plugin-postcss": "^3.1.1", 47 | "rollup-plugin-terser": "^6.1.0", 48 | "rollup-plugin-vue": "^5.1.9", 49 | "tailwindcss": "^1.4.6", 50 | "vue": "^2.6.11", 51 | "vue-server-renderer": "^2.6.11", 52 | "vue-template-compiler": "^2.6.11" 53 | }, 54 | "devDependencies": { 55 | "@commitlint/cli": "latest", 56 | "@commitlint/config-conventional": "latest", 57 | "ava": "latest", 58 | "codecov": "latest", 59 | "cross-env": "latest", 60 | "eslint": "6.x", 61 | "eslint-config-xo-lass": "latest", 62 | "fixpack": "latest", 63 | "husky": "latest", 64 | "lint-staged": "latest", 65 | "nyc": "latest", 66 | "remark-cli": "latest", 67 | "remark-preset-github": "latest", 68 | "xo": "0.25" 69 | }, 70 | "engines": { 71 | "node": ">=8.3" 72 | }, 73 | "homepage": "https://github.com/athif23/vue2html", 74 | "husky": { 75 | "hooks": { 76 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 77 | } 78 | }, 79 | "keywords": [ 80 | "converter", 81 | "html", 82 | "tool", 83 | "vue" 84 | ], 85 | "license": "MIT", 86 | "main": "src/index.js", 87 | "prettier": { 88 | "singleQuote": true, 89 | "bracketSpacing": true, 90 | "trailingComma": "none" 91 | }, 92 | "remarkConfig": { 93 | "plugins": [ 94 | "preset-github" 95 | ] 96 | }, 97 | "repository": { 98 | "type": "git", 99 | "url": "https://github.com/athif23/vue2html" 100 | }, 101 | "scripts": { 102 | "ava": "cross-env NODE_ENV=test ava", 103 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", 104 | "lint": "xo && remark . -qfo", 105 | "nyc": "cross-env NODE_ENV=test nyc ava", 106 | "test": "npm run lint && npm run ava", 107 | "test-coverage": "npm run lint && npm run nyc", 108 | "build": "rollup -c", 109 | "watch": "rollup -c -w" 110 | }, 111 | "xo": { 112 | "prettier": false, 113 | "space": 4, 114 | "rules": { 115 | "object-curly-spacing": "off", 116 | "operator-linebreak": "off", 117 | "space-before-function-paren": "off", 118 | "no-else-return": "off", 119 | "import/no-unassigned-import": "off", 120 | "ava/use-t": "off", 121 | "no-unused-expressions": "off" 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue2html 2 | 3 | [![build status](https://img.shields.io/travis/com/athif23/vue2html.svg)](https://travis-ci.com/athif23/vue2html) 4 | [![code coverage](https://img.shields.io/codecov/c/github/athif23/vue2html.svg)](https://codecov.io/gh/athif23/vue2html) 5 | [![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 6 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 7 | [![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://lass.js.org) 8 | [![license](https://img.shields.io/github/license/athif23/vue2html.svg)](LICENSE) 9 | [![npm downloads](https://img.shields.io/npm/dt/vue2html.svg)](https://npm.im/vue2html) 10 | 11 | > A simple tool to convert vue component to html 12 | 13 | > DISCLAIMER: This library are still in development, so there's many missing features. 14 | 15 | 16 | ## Table of Contents 17 | 18 | * [Install](#install) 19 | * [Usage](#usage) 20 | * [Options](#options) 21 | * [Contributors](#contributors) 22 | * [Known Issues](#known-issues) 23 | * [License](#license) 24 | 25 | 26 | ## Install 27 | 28 | Install `vue2html` globally, 29 | 30 | ```sh 31 | npm install -g vue2html 32 | ``` 33 | 34 | ## Usage 35 | 36 | Now, you just need to pass your `.vue` file as the argument. You can pass as much as you want. Directory or Components paths. 37 | 38 | ```sh 39 | vue2html Component.vue App.vue ./components 40 | ``` 41 | 42 | Use `--help` or `-h` to see all the available options. 43 | ```sh 44 | vue2html --help 45 | ``` 46 | 47 | Or you can also call it programmatically. 48 | 49 | ```sh 50 | npm install vue2html 51 | ``` 52 | 53 | ```js 54 | const { compileToHTML } = require('vue2html'); 55 | const path = require('path'); 56 | 57 | // Single path 58 | compileToHTML('./Component.vue', { 59 | props: {}, 60 | // Pass vue-server-renderer's `context` 61 | context: { 62 | title: 'vue ssr', 63 | metas: ` 64 | 65 | 66 | `, 67 | }, 68 | writeToFile: true 69 | }); 70 | 71 | // Multiple paths 72 | compileToHTML(['./Component.vue', './Header.vue'], { 73 | // Note: the order of the props need to be the same with the order of the paths 74 | props: [ 75 | { name: "Athif Humam", count: 12 }, 76 | { color: 'black' } 77 | ], 78 | writeToFile: true 79 | }); 80 | ``` 81 | 82 | ### Known Issues 83 | * If you use tailwind please remember that it would take more time to generate than without it, especially if you also use purge option. I mean that's just how it should be. I can't change it even if I want to. 84 | 85 | * Component's style included in other components. I don't know why this happens, and I honestly doesn't really want to care much about this right now. But don't worry if you use scoped style, it would be safe even if it included in other components... I think. 86 | 87 | ##### TODO 88 | 89 | * [x] Add css 90 | * [x] Add postcss plugins 91 | * [x] Add Tailwindcss 92 | * [x] Can pass folder path as an argument, like `./components` 93 | * [ ] Add more test 94 | * [ ] Convert raw string instead of file 95 | 96 | > I haven't found a way to do this yet, and as far as I know, rollup only allowed file path to be passed to their input options. If someone know about this, please do tell me or you can just open up a PR. I would really appreciate it :) 97 | 98 | 99 | ## Contributors 100 | 101 | | Name | Website | 102 | | ------------------------ | ---------------------------- | 103 | | **Muhammad Athif Humam** | | 104 | 105 | 106 | ## License 107 | 108 | [MIT](LICENSE) © [Muhammad Athif Humam](https://athif23.github.io/) 109 | -------------------------------------------------------------------------------- /src/compile-to-component.js: -------------------------------------------------------------------------------- 1 | import plugins from './plugins'; 2 | import { 3 | isVueFile, 4 | getComponentName, 5 | parseStrToFunc, 6 | convertSlash, 7 | copyFirstComponent 8 | } from './utils'; 9 | 10 | const { rollup } = require('rollup'); 11 | const path = require('path'); 12 | const fs = require('fs-extra'); 13 | 14 | export default async function(filenames, options = {}) { 15 | if (!filenames) { 16 | return; 17 | } 18 | 19 | let vueComponents; 20 | let imports = ''; 21 | 22 | // This is default options 23 | const defaultOptions = { 24 | props: {}, 25 | plugins: [], 26 | tailwind: false, 27 | purge: false 28 | }; 29 | 30 | // Extend default options 31 | Object.assign(defaultOptions, options); 32 | 33 | if (Array.isArray(filenames)) { 34 | vueComponents = filenames.map(file => { 35 | return { 36 | name: getComponentName(file), 37 | isVue: isVueFile(file), 38 | path: convertSlash( 39 | path.relative(path.resolve(__dirname, '../template'), file) 40 | ), 41 | resolvePath: path.resolve(file) 42 | }; 43 | }); 44 | } else { 45 | const component = { 46 | name: getComponentName(filenames), 47 | isVue: isVueFile(filenames), 48 | path: convertSlash( 49 | path.relative(path.resolve(__dirname, '../template'), filenames) 50 | ), 51 | resolvePath: path.resolve(filenames) 52 | }; 53 | vueComponents = [component]; 54 | } 55 | 56 | let copyResult; 57 | if (defaultOptions.tailwind) { 58 | copyResult = await copyFirstComponent(vueComponents); 59 | 60 | vueComponents = copyResult[0]; 61 | 62 | // I don't know why, but it runs fine with this line. 63 | setTimeout(() => {}, 500); 64 | 65 | const replace = require('replace-in-file'); 66 | await replace({ 67 | files: path.resolve(vueComponents[0].resolvePath), 68 | from: //, 69 | to: match => 70 | match + 71 | `\n@import url('${convertSlash( 72 | path.relative( 73 | path.resolve( 74 | path.dirname(vueComponents[0].resolvePath) 75 | ), 76 | path.resolve( 77 | '../', 78 | __dirname, 79 | '../template', 80 | 'tailwind.css' 81 | ) 82 | ) 83 | )}');\n` 84 | }); 85 | } 86 | 87 | // Import each components 88 | vueComponents.forEach(com => { 89 | imports += `\nimport ${com.name.toLowerCase()} from '${ 90 | com.path 91 | }';\nexport const ${com.name} = ${com.name.toLowerCase()};\n`; 92 | }); 93 | 94 | const templateDir = (file = '') => 95 | path.resolve(__dirname, '../template', file); 96 | 97 | await fs.copy( 98 | templateDir('template.js'), 99 | templateDir('__template.js'), 100 | err => { 101 | if (err) { 102 | throw err; 103 | } 104 | } 105 | ); 106 | 107 | await fs.appendFile(templateDir('__template.js'), imports, err => { 108 | if (err) { 109 | throw err; 110 | } 111 | }); 112 | 113 | // Rollup `input` options 114 | const inputOptions = { 115 | input: templateDir('__template.js'), 116 | plugins: [ 117 | ...defaultOptions.plugins, 118 | ...plugins({ 119 | tailwind: defaultOptions.tailwind, 120 | purge: defaultOptions.purge 121 | }) 122 | ], 123 | external: ['vue'] 124 | }; 125 | // Rollup `output` options 126 | const outputOptions = { 127 | format: 'iife', 128 | name: 'Components', 129 | sourcemap: 'hidden', 130 | globals: { 131 | vue: 'Vue' 132 | } 133 | }; 134 | 135 | const bundle = await rollup(inputOptions); 136 | const { output } = await bundle.generate(outputOptions); 137 | 138 | await fs.remove(templateDir('__template.js')); 139 | // Only run when tailwind options is true 140 | (defaultOptions.tailwind && await fs.remove(copyResult[1])); 141 | 142 | let { code } = output[0]; 143 | 144 | code += '\nreturn Components;'; 145 | 146 | const result = parseStrToFunc(code)(); 147 | 148 | return result; 149 | } 150 | --------------------------------------------------------------------------------