diff --git a/generator/helpers.js b/generator/helpers.js deleted file mode 100644 index 9cf07c91..00000000 --- a/generator/helpers.js +++ /dev/null @@ -1,53 +0,0 @@ -const fs = require('fs') - -module.exports = function (api) { - return { - getMain() { - return api.hasPlugin('typescript') - ? api.resolve('src/main.ts') - : api.resolve('src/main.js') - }, - - updateBabelConfig (callback) { - let config, configPath - - const rcPath = api.resolve('./babel.config.js') - const pkgPath = api.resolve('./package.json') - if (fs.existsSync(rcPath)) { - configPath = rcPath - config = callback(require(rcPath)) - } else if (fs.existsSync(pkgPath)) { - configPath = pkgPath - config = JSON.parse(fs.readFileSync(pkgPath, { encoding: 'utf8' })) - - if (config.babel) { - config.babel = callback(config.babel) - } else { - // TODO: error handling here? - } - } - - if (configPath) { - const moduleExports = configPath !== pkgPath ? 'module.exports = ' : '' - - fs.writeFileSync( - configPath, - `${moduleExports}${JSON.stringify(config, null, 2)}`, - { encoding: 'utf8' } - ) - } else { - // TODO: handle if babel config doesn't exist - } - }, - - updateFile (file, callback) { - let content = fs.existsSync(file) - ? fs.readFileSync(file, { encoding: 'utf8' }) - : '' - - content = callback(content.split(/\r?\n/g)).join('\n') - - fs.writeFileSync(file, content, { encoding: 'utf8' }) - } - } -} diff --git a/generator/index.js b/generator/index.js index b2a07921..6f8e178f 100755 --- a/generator/index.js +++ b/generator/index.js @@ -1,153 +1,23 @@ module.exports = (api, opts, rootOpts) => { - const helpers = require('./helpers')(api) - - api.extendPackage({ - dependencies: { - vuetify: "^1.1.11" - } - }) - - if (opts.useAlaCarte) { - api.extendPackage({ - devDependencies: { - "babel-plugin-transform-imports": "^1.4.1", - "stylus": "^0.54.5", - "stylus-loader": "^3.0.1", - } - }) - } - - if (opts.usePolyfill) { - api.extendPackage({ - devDependencies: { - "@babel/polyfill": "^7.0.0-beta.49", - } - }) - } - - if (api.hasPlugin('electron-builder') === true) { - // material icons pkg for electron - api.extendPackage({ - devDependencies: { - "material-design-icons-iconfont": "^3.0.3", - } - }) - - try { - api.injectImports(helpers.getMain(), - `import 'material-design-icons-iconfont/dist/material-design-icons.css'` - ) - } catch(e) { - console.error(e) - } - } - - - // Render vuetify plugin file - api.render(api.hasPlugin('typescript') ? { - './src/plugins/vuetify.ts': './templates/default/src/plugins/vuetify.ts' - } : { - './src/plugins/vuetify.js': './templates/default/src/plugins/vuetify.js' - }, opts) - - // Render files if we're replacing - const fs = require('fs') - const routerPath = api.resolve('./src/router.js') - opts.router = fs.existsSync(routerPath) - - if (opts.replaceComponents) { - const files = { - './src/App.vue': './templates/default/src/App.vue', - './src/assets/logo.png': './templates/default/src/assets/logo.png' - } - - if (opts.router) { - files['./src/views/Home.vue'] = './templates/default/src/views/Home.vue' - } else { - api.render('./templates/hw') - } - - api.render(files, opts) - } + const alaCarte = require('./tools/alaCarte') + const fonts = require('./tools/fonts') + const polyfill = require('./tools/polyfill') + const vuetify = require('./tools/vuetify') + + vuetify.addDependencies(api) + opts.useAlaCarte && alaCarte.addDependencies(api) + opts.usePolyfill && polyfill.addDependencies(api) + opts.installFonts && fonts.addDependencies(api, opts.iconFont) + opts.installFonts && fonts.addImports(api, opts.iconFont) + vuetify.renderFiles(api, opts) // adapted from https://github.com/Akryum/vue-cli-plugin-apollo/blob/master/generator/index.js#L68-L91 api.onCreateComplete(() => { - // Modify main.js - helpers.updateFile(helpers.getMain(), lines => { - const vueImportIndex = lines.findIndex(line => line.match(/^import Vue/)) - - lines.splice(vueImportIndex + 1, 0, 'import \'./plugins/vuetify\'') - - return lines - }) - - // Add polyfill - if (opts.usePolyfill) { - helpers.updateBabelConfig(cfg => { - if (!cfg.presets) return cfg - - const vuePresetIndex = cfg.presets.findIndex(p => Array.isArray(p) ? p[0] === '@vue/app' : p === '@vue/app') - const isArray = Array.isArray(cfg.presets[vuePresetIndex]) - - if (vuePresetIndex < 0) return cfg - - if (isArray) { - cfg.presets[vuePresetIndex][1]['useBuiltIns'] = 'entry' - } else { - cfg.presets[vuePresetIndex] = [ - '@vue/app', - { - useBuiltIns: 'entry' - } - ] - } - - return cfg - }) - - helpers.updateFile(helpers.getMain(), lines => { - if (!lines.find(l => l.match(/^(import|require).*@babel\/polyfill.*$/))) { - lines.unshift('import \'@babel/polyfill\'') - } - - return lines - }) - } - - // If a-la-carte, update babel - if (opts.useAlaCarte) { - helpers.updateBabelConfig(cfg => { - if (cfg.plugins === undefined) { - cfg.plugins = [] - } - - cfg.plugins.push([ - 'transform-imports', - { - vuetify: { - transform: 'vuetify/es5/components/${member}', - preventFullImport: true - } - } - ]) - - return cfg - }) - } - - // Add Material Icons (unless electron) - if(api.hasPlugin('electron-builder') === false) { - const indexPath = api.resolve('./public/index.html') - - let content = fs.readFileSync(indexPath, { encoding: 'utf8' }) - - const lines = content.split(/\r?\n/g).reverse() - - const lastLink = lines.findIndex(line => line.match(/^\s* + <%_ if (options.iconFont === 'md') { _%> + <%_ } else if (options.iconFont === 'mdi') { _%> + + <%_ } else if (options.iconFont === 'fa') { _%> + + <%_ } else if (options.iconFont === 'fa4') { _%> + + <%_ } _%> + <%_ if (options.iconFont === 'md') { _%> web + <%_ } else if (options.iconFont === 'mdi') { _%> + mdi-web + <%_ } else if (options.iconFont === 'fa') { _%> + fas fa-globe + <%_ } else if (options.iconFont === 'fa4') { _%> + fa-globe + <%_ } _%> - remove + <%_ if (options.iconFont === 'md') { _%> + web + <%_ } else if (options.iconFont === 'mdi') { _%> + mdi-minus + <%_ } else if (options.iconFont === 'fa') { _%> + fas fa-minus + <%_ } else if (options.iconFont === 'fa4') { _%> + fa-minus + <%_ } _%> + <%_ if (options.iconFont === 'md') { _%> menu + <%_ } else if (options.iconFont === 'mdi') { _%> + mdi-menu + <%_ } else if (options.iconFont === 'fa') { _%> + fas fa-bars + <%_ } else if (options.iconFont === 'fa4') { _%> + fa-bars + <%_ } _%> @@ -61,7 +93,15 @@ + <%_ if (options.iconFont === 'md') { _%> compare_arrows + <%_ } else if (options.iconFont === 'mdi') { _%> + mdi-arrows-left-right-bold-outline + <%_ } else if (options.iconFont === 'fa') { _%> + fas fa-exchange-alt + <%_ } else if (options.iconFont === 'fa4') { _%> + fa-exchange + <%_ } _%> Switch drawer (click me) @@ -91,7 +131,15 @@ export default { drawer: true, fixed: false, items: [{ +<%_ if (options.iconFont === 'md') { _%> icon: 'bubble_chart', +<%_ } else if (options.iconFont === 'mdi') { _%> + icon: 'mdi-chart-bubble', +<%_ } else if (options.iconFont === 'fa') { _%> + icon: 'fas fa-shapes', +<%_ } else if (options.iconFont === 'fa4') { _%> + icon: 'fa-bullseye', +<%_ } _%> title: 'Inspire' }], miniVariant: false, diff --git a/generator/templates/default/src/plugins/vuetify.js b/generator/templates/default/src/plugins/vuetify.js index 5db9bf37..a3f82dc1 100644 --- a/generator/templates/default/src/plugins/vuetify.js +++ b/generator/templates/default/src/plugins/vuetify.js @@ -1,46 +1,60 @@ -import Vue from 'vue' -<%_ if (options.useAlaCarte) { _%> -import { - Vuetify, - VApp, - VNavigationDrawer, - VFooter, - VList, - VBtn, - VIcon, - VGrid, - VToolbar, - transitions -} from 'vuetify' -import 'vuetify/src/stylus/app.styl' -<%_ } else { _%> -import Vuetify from 'vuetify' -import 'vuetify/dist/vuetify.min.css' -<%_ } _%> - -Vue.use(Vuetify, { -<%_ if (options.useAlaCarte) { _%> - components: { - VApp, - VNavigationDrawer, - VFooter, - VList, - VBtn, - VIcon, - VGrid, - VToolbar, - transitions - }, -<%_ } _%> -<%_ if (options.useTheme) { _%> - theme: { - primary: '#ee44aa', - secondary: '#424242', - accent: '#82B1FF', - error: '#FF5252', - info: '#2196F3', - success: '#4CAF50', - warning: '#FFC107' - }, -<%_ } _%> -}) +import Vue from 'vue' +<%_ if (useAlaCarte) { _%> +import { + Vuetify, + VApp, + VNavigationDrawer, + VFooter, + VList, + VBtn, + VIcon, + VGrid, + VToolbar, + transitions +} from 'vuetify' +import 'vuetify/src/stylus/app.styl' +<%_ } else { _%> +import Vuetify from 'vuetify' +import 'vuetify/dist/vuetify.min.css' +<%_ } _%> +<%_ if (locale !== 'en') { _%> +import <%= locale.replace(/-/g, '') %> from 'vuetify/<%= typescript ? 'src' : 'es5' %>/locale/<%= locale %>' +<%_ } _%> + +Vue.use(Vuetify, { +<%_ if (useAlaCarte) { _%> + components: { + VApp, + VNavigationDrawer, + VFooter, + VList, + VBtn, + VIcon, + VGrid, + VToolbar, + transitions + }, +<%_ } _%> +<%_ if (useTheme) { _%> + theme: { + primary: '#ee44aa', + secondary: '#424242', + accent: '#82B1FF', + error: '#FF5252', + info: '#2196F3', + success: '#4CAF50', + warning: '#FFC107' + }, +<%_ } _%> +<%_ if (useCustomProperties) { _%> + customProperties: true, +<%_ } _%> + iconfont: '<%= iconFont %>', +<%_ if (locale !== 'en') { _%> + lang: { + locales: { <%= locale.replace(/-/g, '') %> }, + current: '<%= locale %>' + }, +<%_ } _%> +}) + diff --git a/generator/templates/default/src/plugins/vuetify.ts b/generator/templates/default/src/plugins/vuetify.ts deleted file mode 100644 index 7ecb4203..00000000 --- a/generator/templates/default/src/plugins/vuetify.ts +++ /dev/null @@ -1,46 +0,0 @@ -import Vue from 'vue' -<%_ if (options.useAlaCarte) { _%> -import { - Vuetify, - VApp, - VNavigationDrawer, - VFooter, - VList, - VBtn, - VIcon, - VGrid, - VToolbar, - transitions -} from 'vuetify' -import 'vuetify/src/stylus/app.styl' -<%_ } else { _%> -import Vuetify from 'vuetify' -import 'vuetify/dist/vuetify.min.css' -<%_ } _%> - -Vue.use(Vuetify, { -<%_ if (options.useAlaCarte) { _%> - components: { - VApp, - VNavigationDrawer, - VFooter, - VList, - VBtn, - VIcon, - VGrid, - VToolbar, - transitions - }, -<%_ } _%> -<%_ if (options.useTheme) { _%> - theme: { - primary: '#ee44aa', - secondary: '#424242', - accent: '#82B1FF', - error: '#FF5252', - info: '#2196F3', - success: '#4CAF50', - warning: '#FFC107' - }, -<%_ } _%> -}) diff --git a/generator/tools/alaCarte.js b/generator/tools/alaCarte.js new file mode 100644 index 00000000..a3b03f4e --- /dev/null +++ b/generator/tools/alaCarte.js @@ -0,0 +1,43 @@ +const helpers = require('./helpers') + +function addDependencies (api) { + api.extendPackage({ + devDependencies: { + "babel-plugin-transform-imports": "^1.5.0", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.1", + } + }) +} + +function updateBabelConfig (api) { + helpers.updateBabelConfig(api, cfg => { + if (cfg.plugins === undefined) { + cfg.plugins = [] + } + + cfg.plugins.push([ + 'transform-imports', + { + vuetify: { + transform: 'vuetify/es5/components/${member}', + preventFullImport: true + } + } + ]) + + return cfg + }) + + + helpers.updateFile(api, './babel.config.js', lines => { + const index = lines.findIndex(line => line.indexOf('vuetify/es5/components/${member}') !== -1) + index && (lines[index] += ' // eslint-disable-line no-template-curly-in-string') + return lines + }) +} + +module.exports = { + addDependencies, + updateBabelConfig, +} diff --git a/generator/tools/fonts.js b/generator/tools/fonts.js new file mode 100644 index 00000000..bac167ca --- /dev/null +++ b/generator/tools/fonts.js @@ -0,0 +1,73 @@ +const helpers = require('./helpers') +const fonts = { + md: { + 'package': { + 'material-design-icons-iconfont': '^3.0.3', + }, + 'import': 'material-design-icons-iconfont/dist/material-design-icons.css', + link: '', + }, + mdi: { + 'package': { + '@mdi/font': '^2.6.95', + }, + 'import': '@mdi/font/css/materialdesignicons.css', + link: '', + }, + fa: { + 'package': { + '@fortawesome/fontawesome-free': '^5.2.0', + }, + 'import': '@fortawesome/fontawesome-free/css/all.css', + link: '', + }, + fa4: { + 'package': { + 'font-awesome': '^4.7.0', + }, + 'import': 'font-awesome/css/font-awesome.css', + link: '', + }, + roboto: { + 'package': { + 'roboto-fontface': '*', + }, + 'import': 'roboto-fontface/css/roboto/roboto-fontface.css', + link: '', + } +} + +function addDependencies (api, iconFont) { + api.extendPackage({ + dependencies: { + ...fonts['roboto']['package'], + ...fonts[iconFont]['package'], + } + }) +} + +function addImports (api, iconFont) { + try { + api.injectImports(api.entryFile, `import '${fonts['roboto']['import']}'`) + api.injectImports(api.entryFile, `import '${fonts[iconFont]['import']}'`) + } catch(e) { + console.error(e) + } +} + +function addLinks (api, iconFont) { + helpers.updateFile(api, './public/index.html', lines => { + const lastLink = lines.reverse().findIndex(line => line.match(/^\s*<\/head>/)) + + lines.splice(lastLink + 1, 0, ` ${fonts['roboto'].link}`) + lines.splice(lastLink + 1, 0, ` ${fonts[iconFont].link}`) + + return lines.reverse() + }) +} + +module.exports = { + addDependencies, + addImports, + addLinks, +} diff --git a/generator/tools/helpers.js b/generator/tools/helpers.js new file mode 100644 index 00000000..545c2316 --- /dev/null +++ b/generator/tools/helpers.js @@ -0,0 +1,49 @@ +const fs = require('fs') + +function updateBabelConfig (api, callback) { + let config, configPath + + const rcPath = api.resolve('./babel.config.js') + const pkgPath = api.resolve('./package.json') + if (fs.existsSync(rcPath)) { + configPath = rcPath + config = callback(require(rcPath)) + } else if (fs.existsSync(pkgPath)) { + configPath = pkgPath + config = JSON.parse(fs.readFileSync(pkgPath, { encoding: 'utf8' })) + + if (config.babel) { + config.babel = callback(config.babel) + } else { + // TODO: error handling here? + } + } + + if (configPath) { + const moduleExports = configPath !== pkgPath ? 'module.exports = ' : '' + + fs.writeFileSync( + configPath, + `${moduleExports}${JSON.stringify(config, null, 2)}`, + { encoding: 'utf8' } + ) + } else { + // TODO: handle if babel config doesn't exist + } +} + +function updateFile (api, file, callback) { + file = api.resolve(file) + let content = fs.existsSync(file) + ? fs.readFileSync(file, { encoding: 'utf8' }) + : '' + + content = callback(content.split(/\r?\n/g)).join('\n') + + fs.writeFileSync(file, content, { encoding: 'utf8' }) +} + +module.exports = { + updateBabelConfig, + updateFile, +} diff --git a/generator/tools/polyfill.js b/generator/tools/polyfill.js new file mode 100644 index 00000000..cba1f4c4 --- /dev/null +++ b/generator/tools/polyfill.js @@ -0,0 +1,49 @@ +const helpers = require('./helpers') + +function addDependencies (api) { + api.extendPackage({ + dependencies: { + "@babel/polyfill": "^7.0.0-rc.1", + } + }) +} + +function updateBabelConfig (api) { + helpers.updateBabelConfig(api, cfg => { + if (!cfg.presets) return cfg + + const vuePresetIndex = cfg.presets.findIndex(p => Array.isArray(p) ? p[0] === '@vue/app' : p === '@vue/app') + const isArray = Array.isArray(cfg.presets[vuePresetIndex]) + + if (vuePresetIndex < 0) return cfg + + if (isArray) { + cfg.presets[vuePresetIndex][1]['useBuiltIns'] = 'entry' + } else { + cfg.presets[vuePresetIndex] = [ + '@vue/app', + { + useBuiltIns: 'entry' + } + ] + } + + return cfg + }) +} + +function addImports (api) { + helpers.updateFile(api, api.entryFile, lines => { + if (!lines.find(l => l.match(/^(import|require).*@babel\/polyfill.*$/))) { + lines.unshift('import \'@babel/polyfill\'') + } + + return lines + }) +} + +module.exports = { + addDependencies, + updateBabelConfig, + addImports, +} diff --git a/generator/tools/vuetify.js b/generator/tools/vuetify.js new file mode 100755 index 00000000..8f1edcbe --- /dev/null +++ b/generator/tools/vuetify.js @@ -0,0 +1,69 @@ +const helpers = require('./helpers') + +function addDependencies (api) { + api.extendPackage({ + dependencies: { + vuetify: "^1.2.0" + } + }) +} + +function renderFiles (api, opts) { + const pluginFilename = api.hasPlugin('typescript') ? 'vuetify.ts' : 'vuetify.js' + const pluginSourceFilename = 'vuetify.js' + api.render({ + [`./src/plugins/${pluginFilename}`]: `../templates/default/src/plugins/${pluginSourceFilename}` + }, { + ...opts, + typescript: api.hasPlugin('typescript') + }) + + // Render files if we're replacing + const fs = require('fs') + const routerPath = api.resolve('./src/router.js') + opts.router = fs.existsSync(routerPath) + + if (opts.replaceComponents) { + const files = { + './src/App.vue': '../templates/default/src/App.vue', + './src/assets/logo.png': '../templates/default/src/assets/logo.png' + } + + if (opts.router) { + files['./src/views/Home.vue'] = '../templates/default/src/views/Home.vue' + } else { + api.render('../templates/hw') + } + + api.render(files, opts) + } +} + +function addImports (api) { + helpers.updateFile(api, api.entryFile, lines => { + const vueImportIndex = lines.findIndex(line => line.match(/^import Vue/)) + + lines.splice(vueImportIndex + 1, 0, `import './plugins/vuetify'`) + + return lines + }) +} + +function setHtmlLang (api, locale) { + helpers.updateFile(api, './public/index.html', lines => { + const htmlIndex = lines.findIndex(line => line.match(/]+(\s|>)/)) + + if (htmlIndex !== -1) { + lines[htmlIndex] = lines[htmlIndex].replace(/(]+)(\s|>)/, `$1$3"${locale}"$5`) + } + + return lines + }) +} + +module.exports = { + addDependencies, + addImports, + renderFiles, + setHtmlLang, +} diff --git a/prompts.js b/prompts.js index 653c3e9b..c9a36dc8 100644 --- a/prompts.js +++ b/prompts.js @@ -11,6 +11,38 @@ module.exports = [ message: 'Use custom theme?', default: false, }, + { + name: 'useCustomProperties', + type: 'confirm', + message: 'Use custom properties (CSS variables)?', + default: false, + }, + { + name: 'iconFont', + type: 'rawlist', + message: 'Select icon font', + choices: [ + 'Material Icons (default)', + 'Material Design Icons', + 'Font Awesome 5', + 'Font Awesome 4', + ], + default: 0, + filter: function (val) { + return { + 'Material Icons (default)': 'md', + 'Material Design Icons': 'mdi', + 'Font Awesome 5': 'fa', + 'Font Awesome 4': 'fa4', + }[val] + } + }, + { + name: 'installFonts', + type: 'confirm', + message: 'Use fonts as a dependency (for Electron or offline)?', + default: false, + }, { name: 'useAlaCarte', type: 'confirm', @@ -22,5 +54,43 @@ module.exports = [ type: 'confirm', message: 'Use babel/polyfill?', default: true + }, + { + name: 'locale', + type: 'rawlist', + message: 'Select locale', + choices: [ + 'English (default)', + 'Catalan', + 'Chinese (simplified)', + 'Chinese (traditional)', + 'Dutch', + 'Farsi', + 'French', + 'German', + 'Greek', + 'Polish', + 'Portuguese', + 'Russian', + 'Ukrainian', + ], + default: 0, + filter: function (val) { + return { + 'English (default)': 'en', + 'Catalan': 'ca', + 'Chinese (simplified)': 'zh-Hans', + 'Chinese (traditional)': 'zh-Hant', + 'Dutch': 'nl', + 'Farsi': 'fa', + 'French': 'fr', + 'German': 'de', + 'Greek': 'gr', + 'Polish': 'pl', + 'Portuguese': 'pt', + 'Russian': 'ru', + 'Ukrainian': 'uk', + }[val] + } } ] diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 00000000..877fbbba --- /dev/null +++ b/vue.config.js @@ -0,0 +1,11 @@ +const path = require('path') + +module.exports = { + configureWebpack: { + resolve: { + alias: { + 'vue$': path.resolve('./node_modules/vue/dist/vue.common.js'), + }, + }, + }, +}