From b6d156c344971e64d514aec722a098e7f82ca0d5 Mon Sep 17 00:00:00 2001 From: Paolo Caleffi Date: Tue, 14 Apr 2020 09:43:03 +0200 Subject: [PATCH] feat: one-shot TS upgrade for existing codebases (#48) * feat: enhance upgrade algorithm with latest core integrations * docs(README): add some info and deprecation notice * feat(code-update): apply store and route helpers (#1) * feat(code-update): apply store and route helpers * refactor: solve TODOs * chore(linting): revert quasar eslint package usage --- README.md | 31 +- extension/README.md | 3 - extension/package.json | 10 +- extension/src/index.js | 26 +- extension/src/install.js | 507 ++++++++++++------ extension/src/prompts.js | 33 +- extension/src/templates/base/_eslintrc.js | 91 ++++ .../src/templates/base/components/models.ts | 8 + extension/src/templates/base/tsconfig.json | 18 +- .../class-api/components/ClassComponent.vue | 40 ++ .../components/CompositionComponent.vue | 61 +++ .../src/templates/noprettier/_eslintrc.js | 70 --- extension/src/templates/prettier/_eslintrc.js | 76 --- 13 files changed, 605 insertions(+), 369 deletions(-) delete mode 100644 extension/README.md create mode 100644 extension/src/templates/base/_eslintrc.js create mode 100644 extension/src/templates/base/components/models.ts create mode 100644 extension/src/templates/class-api/components/ClassComponent.vue create mode 100644 extension/src/templates/composition-api/components/CompositionComponent.vue delete mode 100644 extension/src/templates/noprettier/_eslintrc.js delete mode 100644 extension/src/templates/prettier/_eslintrc.js diff --git a/README.md b/README.md index 2eaabe2..b38f6f5 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,28 @@ -# quasar-app-extension-typescript [wip] +# quasar-app-extension-typescript > ### WARNING > -> This app extension is out of date with the Current version of Quasar app-v1.6.0 and will may not work as expected. This extension will be updated shortly. -> An updated starter that support Typescript natively is currently being tested and should be integrated with the `Quasar create` command soon. If you would like to try it out now run the following command `quasar create --kit IlCallo/quasar-starter-kit --branch dev` +> This extension will bring you in sync with Quasar + TS setup as of `quasar@1.9.x` and `@quasar/app@1.6.x` +> +> This extension is DEPRECATED and is only meant to ease the transition of pre-existing codebases. For new projects use starter-kit `TypeScript` option +> +> ESLint configuration will be overwritten, only Prettier flavour is supported out-of-the-box. +> `airbnb` and `default` configuration should be manually merged using your previous `.eslintrc.js` or one generated when creating a new Quasar project. +> +> You need to remove this extension and `typescript` dependency after a successful installation: the extension will only do the migration and there's no point into keeping it after that, while `typescript` dependency is already provided by `@quasar/app` +> +> **This extension is one-shot and does not provide uninstall script, make sure you've some form of version control in place to perform rollbacks** + +Add TypeScript to your Quasar project (won't work for 0.x Quasar versions). + +For simpler cases, just running this extension will do the job. -Add typescript to your Quasar 1.0 project +For the majority of cases, many tweaks on your side will be needed to fix type or linting issues, take some time when trying the migration. -visit https://hackmd.io/Ypx6VMWUQp6R1iPyCvRHWQ?edit to join the conversation +If you don't trust a software to automatically update your code, you can manually update your project following the [official documentation](https://quasar.dev/quasar-cli/cli-documentation/supporting-ts#Installation-of-TypeScript-Support). ## Installation + Add the app extension to your project: ```shell @@ -17,7 +30,15 @@ $ quasar ext add @quasar/typescript ``` To test the various build types, cd into test-extension and: + ``` $ yarn $ quasar ext invoke @quasar/typescript ``` + +## Fallback + +If you experience problems you cannot understand or resolve, copy your `src` folder (and any project specific configuration) into a freshly created project with `TypeScript` option enabled. +Then proceed to manually update your files to use TypeScript. + +To create a new project, run `quasar create ` using the global Quasar CLI and enable the `TypeScript` option. diff --git a/extension/README.md b/extension/README.md deleted file mode 100644 index 368cc79..0000000 --- a/extension/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Quasar CLI Extension - -> WIP diff --git a/extension/package.json b/extension/package.json index fef5b95..9dc912c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -22,13 +22,7 @@ "main": "src/index.js", "dependencies": { "execa": "^3.2.0", - "fork-ts-checker-webpack-plugin": "^1.5.1", - "glob": "^7.1.4", - "ts-loader": "^6.2.0", - "vue-class-component": "^7.1.0", - "vue-property-decorator": "^8.3.0", - "vue-template-compiler": "^2.6.10" + "glob": "^7.1.4" }, - "devDependencies": { - } + "devDependencies": {} } diff --git a/extension/src/index.js b/extension/src/index.js index 93a31cc..a1e9a71 100644 --- a/extension/src/index.js +++ b/extension/src/index.js @@ -5,28 +5,4 @@ * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/IndexAPI.js */ -module.exports = function(api, ctx) { - api.chainWebpack((chain, invoke) => { - const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin') - const useForkTsChecker = api.prompts.webpack === 'plugin' - - chain.resolve.extensions.add('.ts').add('.tsx') - chain.module - .rule('typescript') - .test(/\.tsx?$/) - .use('ts-loader') - .loader('ts-loader') - .options({ - appendTsSuffixTo: [/\.vue$/], - onlyCompileBundledFiles: !useForkTsChecker, - // Type checking is handled by fork-ts-checker-webpack-plugin - transpileOnly: useForkTsChecker - }) - if (useForkTsChecker) { - chain - .plugin('ts-checker') - // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options - .use(ForkTsCheckerWebpackPlugin, [{ eslint: true, vue: true }]) - } - }) -} +module.exports = function() {} diff --git a/extension/src/install.js b/extension/src/install.js index 34e9986..0530c66 100644 --- a/extension/src/install.js +++ b/extension/src/install.js @@ -4,189 +4,384 @@ * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/InstallAPI.js */ const execa = require('execa') +const fs = require('fs') +const path = require('path') -module.exports = async api => { - // TODO we need a way to detect if ESLint was configured (maybe checking for .eslintrc.js? Or checking if ESLint dependency is present) - // And if it is not, we must add it to the devDeps +function abort(message) { + throw new Error(message + ', aborting TS upgrade') +} - let devDependencies = { - '@types/node': '11.9.5', - '@typescript-eslint/eslint-plugin': '^1.12.0', - '@typescript-eslint/parser': '^1.12.0', - typescript: '^3.3.3' +function extendPackageJson(api) { + const dependencies = { + quasar: '^1.9.2', } - if(api.prompts.prettier) { - devDependencies['eslint-config-prettier'] = '^6.0.0' + + const devDependencies = { + '@quasar/app': '^1.6.0', + '@types/node': '^10.17.15', + '@typescript-eslint/eslint-plugin': '^2.22.0', + '@typescript-eslint/parser': '^2.22.0', + 'eslint-plugin-vue': '^6.2.1', + ...(api.prompts.componentStyle === 'composition' + ? { '@vue/composition-api': '^0.5.0' } + : {}), + ...(api.prompts.componentStyle === 'class' + ? { 'vue-class-component': '^7.2.2', 'vue-property-decorator': '^8.3.0' } + : {}), + ...(api.prompts.prettier ? { 'eslint-config-prettier': '^6.10.0' } : {}), + ...(!api.hasPackage('eslint', '>=6') ? { eslint: '^6.8.0' } : {}), } - api.render('./templates/base', {}, true) api.extendPackageJson({ + dependencies, + devDependencies, scripts: { - lint: 'eslint --ext .js,.ts,.vue --ignore-path .gitignore ./' + lint: 'eslint --ext .js,.ts,.vue --ignore-path .gitignore ./', }, - devDependencies }) +} - // TODO is there a way to automatically detect VSCode usage? Checking .vscode folder isn't a reliable indicator - if (api.prompts.vscode) { - const fs = require('fs') +function vsCodeConfiguration(api) { + if (!fs.existsSync('.vscode')) { + fs.mkdirSync('.vscode') + } - if (!fs.existsSync('.vscode')) { - fs.mkdirSync('.vscode') - } + if (!fs.existsSync('.vscode/settings.json')) { + fs.writeFileSync('.vscode/settings.json', '{}') + } - if (!fs.existsSync('.vscode/settings.json')) { - fs.writeFileSync('.vscode/settings.json', '{}') - } + api.extendJsonFile('.vscode/settings.json', { + 'vetur.experimental.templateInterpolationService': true, + 'vetur.validation.template': false, + 'eslint.validate': ['javascript', 'javascriptreact', 'typescript', 'vue'], + 'typescript.tsdk': 'node_modules/typescript/lib', + ...(api.prompts.prettier ? { 'vetur.format.enable': false } : {}), + }) - api.extendJsonFile('.vscode/settings.json', { - 'vetur.experimental.templateInterpolationService': true, - 'vetur.validation.template': false, - 'eslint.validate': [ - 'javascript', - 'javascriptreact', - 'typescript', - { language: 'vue', autoFix: true } - ] - }) + if (!fs.existsSync('.vscode/extensions.json')) { + fs.writeFileSync('.vscode/extensions.json', '{}') + } - if (!fs.existsSync('.vscode/extensions.json')) { - fs.writeFileSync('.vscode/extensions.json', '{}') - } + api.extendJsonFile('.vscode/extensions.json', { + recommendations: [ + ...(api.prompts.prettier ? ['esbenp.prettier-vscode'] : []), + 'dbaeumer.vscode-eslint', + 'octref.vetur', + ], + unwantedRecommendations: [ + 'hookyqr.beautify', + 'dbaeumer.jshint', + 'ms-vscode.vscode-typescript-tslint-plugin', + ], + }) +} - api.extendJsonFile('.vscode/extensions.json', { - recommendations: [ - 'esbenp.prettier-vscode', - 'dbaeumer.vscode-eslint', - 'octref.vetur' - ], - unwantedRecommendations: [ - 'hookyqr.beautify', - 'dbaeumer.jshint', - 'ms-vscode.vscode-typescript-tslint-plugin' - ] - }) +function addConfigureHelper(quasarConfigText) { + // We immediately return if configure() helper is already there + if (quasarConfigText.includes('module.exports = configure')) { + return quasarConfigText } - if (api.prompts.prettier) { - api.render('./templates/prettier') - } else { - api.render('./templates/noprettier') - } - - if (api.prompts.rename) { - const util = require('util'); - const glob = util.promisify(require('glob')) - const fs = require('fs') - const path = require('path') - - const quasarConfigPath = api.resolve.app('./quasar.conf.js') - const replaceRegex = /module\.exports = function \((ctx)?\) {\n\s*return {/ - let quasarConfig = fs.readFileSync(quasarConfigPath, 'utf8') - if (!replaceRegex.test(quasarConfig)) { - console.log(` -We could not automatically update your quasar.conf.js to -use typescript. Quasar looks for *.js files by default. -Please add this to your quasar.conf.js: - -sourceFiles: { - router: 'src/router/index.ts', - store: 'src/store/index.ts' + const replaceRegex = /module\.exports = function/ + + if (!replaceRegex.test(quasarConfigText)) { + abort('could not find default exported function') + } + + quasarConfigText = quasarConfigText.replace( + replaceRegex, + `const { configure } = require('quasar/wrappers');\n\nmodule.exports = configure(function` + ) + + index = 1 + while (quasarConfigText[quasarConfigText.length - index] !== '}') { + index++ + } + + // Adds the closing `configure()` parenthesis, removing the ending semicolon if present + quasarConfigText = + (quasarConfigText[quasarConfigText.length - index + 1] === ';' + ? quasarConfigText.slice(0, -index + 1) + : quasarConfigText) + ')' + + return quasarConfigText } -`) - } - quasarConfig = quasarConfig.replace( - replaceRegex, - `module.exports = function (ctx) { - return { - // Quasar looks for *.js files by default - sourceFiles: { - router: 'src/router/index.ts', - store: 'src/store/index.ts' - },` - ) - fs.writeFileSync(quasarConfigPath, quasarConfig) - - // List of files that export Vue instance - const vueComponentScriptFiles = [] - - const vueFiles = await glob(api.resolve.app('src/**/*.vue')) - vueFiles.forEach(file => { - const fileDir = path.parse(file).dir - let text = fs.readFileSync(file, 'utf8') - - text = text.replace(//, tag => { - tag = tag.replace(/lang="(js|javascript)" ?/, '') - tag = tag.replace(/src="(.*)\.js"/, (tag, fileName) => { - // Record that file exports a Vue instance - vueComponentScriptFiles.push(path.join(fileDir, fileName + '.js')) - return `src="${fileName}.ts"` - }) - return tag.replace(')/s, - (match, componentData, extra) => - `import Vue from 'vue'\n\nexport default Vue.extend({${componentData}})${extra}` - ) - fs.writeFileSync(file, text) - }) - const jsFiles = await glob(api.resolve.app('src/**/*.js')) +function addSupportTSFlag(quasarConfigText) { + // We immediately return if "supportTS" options is already enabled + if (quasarConfigText.includes('supportTS')) { + return quasarConfigText + } + + const replaceRegex = /return {/ + + if (!replaceRegex.test(quasarConfigText)) { + abort('could not find configuration return statement') + } + + return quasarConfigText.replace( + replaceRegex, + `return { + // https://quasar.dev/quasar-cli/cli-documentation/supporting-ts + supportTS: { + enable: true, + tsCheckerConfig: { eslint: true } + }, + ` + ) +} + +function disableBuildLintingOnDev(quasarConfigText) { + // Get eslint-loader rule addition code, while avoiding all preceding and succeeding rules additions + const replaceRegex = /cfg\.module\.rules\.push\({(?!.*?cfg\.module\.rules\.push.*?cfg\.module\.rules\.push.*?).*?eslint-loader.*?}\);/s + + return quasarConfigText.replace( + replaceRegex, + (match) => `if (process.env.NODE_ENV === 'production') {\n${match}\n}` + ) +} + +function addBootHelper(api) { + const bootFolder = api.resolve.src('boot') + const bootFiles = fs + .readdirSync(bootFolder) + .filter((fileName) => fileName !== '.gitkeep') + .map((fileName) => path.join(bootFolder, fileName)) + + for (const bootFile of bootFiles) { + let bootFileContent = fs.readFileSync(bootFile, 'utf8') - jsFiles.forEach(file => { - let text = fs.readFileSync(file, 'utf8') - // Only change files that export a Vue instance - if (vueComponentScriptFiles.includes(file)) { - text = text.replace( - /export default {(.*)}/s, - (match, componentData) => - `import Vue from 'vue'\n\nexport default Vue.extend({${componentData}})` + if (!bootFileContent.includes(`declare module 'vue/types/vue' {`)) { + if (bootFile.includes('boot/axios')) { + bootFileContent = bootFileContent.replace( + 'export default', + `declare module 'vue/types/vue' {\ninterface Vue {\ni18n: VueI18n;\n}\n}\n\nexport default` ) } - const newFile = path.parse(file) - newFile.ext = '.ts' - delete newFile.base - // Write new file - fs.writeFileSync(path.format(newFile), text) - // Remove old file - fs.unlinkSync(file) - }) - try { - const routesFilePath = api.resolve.app('./src/router/routes.ts') - let routesFile = fs.readFileSync(routesFilePath, 'utf8') - routesFile = `import { RouteConfig } from 'vue-router'\n` + routesFile - routesFile = routesFile.replace( - 'const routes = [', - 'const routes: RouteConfig[] = [' - ) - fs.writeFileSync(routesFilePath, routesFile) - } catch (e) { - console.log('Could not add types to routes.ts') + if (bootFile.includes('boot/i18n')) { + bootFileContent = bootFileContent.replace( + 'export default', + `import { AxiosInstance } from 'axios';\n\ndeclare module 'vue/types/vue' {\ninterface Vue {\n$axios: AxiosInstance;\n}\n}\n\nexport default` + ) + } } - try { - const routerFilePath = api.resolve.app('./src/router/index.ts') - let routerFile = fs.readFileSync(routerFilePath, 'utf8') - routerFile = routerFile.replace( - 'scrollBehavior: () => ({ y: 0 }),', - 'scrollBehavior: () => ({ y: 0, x: 0 }),' - ) - fs.writeFileSync(routerFilePath, routerFile) - } catch (e) { - console.log('Could not add types to router.ts') + // We skip the file if boot() helper is already there or if no function is returned + if ( + bootFileContent.includes('export default boot') || + !bootFileContent.includes('export default') + ) { + continue } + + bootFileContent = + bootFileContent.replace( + 'export default', + `import { boot } from 'quasar/wrappers';\n\nexport default boot(` + ) + ')' + + fs.writeFileSync(bootFile, bootFileContent) } +} + +function addRouteHelper(api) { + const routerFilePath = api.resolve.src('./router/index.js') + let routerFileContent = fs.readFileSync(routerFilePath, 'utf8') + + // We skip the file if route() helper is already there or if no function is returned + if ( + routerFileContent.includes('export default route') || + !routerFileContent.includes('export default') + ) { + return + } + + routerFileContent = + routerFileContent.replace( + 'export default', + `import { route } from 'quasar/wrappers';\n\nexport default route(` + ) + ')' - // TODO: detect if npm or yarn was used - // TODO: some files need typings for theirs parameters (eg. axios and i18n boot files) - execa('yarn').then(() => - execa('yarn', ['lint', '--fix']).catch(() => { - // We'll always get some lint errors until we switch all files to ES6 syntax - // or programmatically add `eslint-env node` around and `eslint-disable-next-line` for - // problematic points + fs.writeFileSync(routerFilePath, routerFileContent) +} + +function addStoreHelper(api) { + const storeFilePath = api.resolve.src('./store/index.js') + // We skip the update if store hasn't been used + if (!fs.existsSync(storeFilePath)) { + return + } + + let storeFileContent = fs.readFileSync(storeFilePath, 'utf8') + + // We skip the file if route() helper is already there or if no function is returned + if ( + storeFileContent.includes('export default store') || + !storeFileContent.includes('export default') + ) { + return + } + + storeFileContent = + storeFileContent.replace( + 'export default', + `import { store } from 'quasar/wrappers';\n\nexport default store(` + ) + ')' + + fs.writeFileSync(storeFilePath, storeFileContent) +} + +async function updateCode(api) { + const util = require('util') + const glob = util.promisify(require('glob')) + + const quasarConfigPath = api.resolve.app('./quasar.conf.js') + let quasarConfigText = fs.readFileSync(quasarConfigPath, 'utf8') + + quasarConfigText = addConfigureHelper(quasarConfigText) + quasarConfigText = addSupportTSFlag(quasarConfigText) + quasarConfigText = disableBuildLintingOnDev(quasarConfigText) + + fs.writeFileSync(quasarConfigPath, quasarConfigText) + + addBootHelper(api) + addRouteHelper(api) + addStoreHelper(api) + + // List of files that export Vue instance + const vueComponentScriptFiles = [] + // We cannot transform object API into class API, + // so we just wrap those into Object wrapper functions + const wrapperFunction = + api.prompts.componentStyle === 'composition' + ? `import { defineComponent } from '@vue/composition-api'\n\nexport default defineComponent` + : `import Vue from 'vue'\n\nexport default Vue.extend` + + const vueFiles = await glob(api.resolve.app('src/**/*.vue')) + vueFiles.forEach((file) => { + const fileDir = path.parse(file).dir + let text = fs.readFileSync(file, 'utf8') + + text = text.replace(//, (tag) => { + tag = tag.replace(/lang="(js|javascript)" ?/, '') + tag = tag.replace(/src="(.*)\.js"/, (tag, fileName) => { + // Record that file exports a Vue instance + vueComponentScriptFiles.push(path.join(fileDir, fileName + '.js')) + return `src="${fileName}.ts"` + }) + return tag.replace(')/s, + (match, componentData, extra) => + `${wrapperFunction}({${componentData}})${extra}` + ) + fs.writeFileSync(file, text) + }) + + const jsFiles = await glob(api.resolve.app('src/**/*.js')) + + jsFiles.forEach((file) => { + let text = fs.readFileSync(file, 'utf8') + // Only change files that export a Vue instance + if (vueComponentScriptFiles.includes(file)) { + text = text.replace( + /export default {(.*)}/s, + (match, componentData) => `${wrapperFunction}({${componentData}})` + ) + } + const newFile = path.parse(file) + newFile.ext = '.ts' + delete newFile.base + // Write new file + fs.writeFileSync(path.format(newFile), text) + // Remove old file + fs.unlinkSync(file) + }) + + try { + const routesFilePath = api.resolve.app('./src/router/routes.ts') + let routesFile = fs.readFileSync(routesFilePath, 'utf8') + routesFile = `import { RouteConfig } from 'vue-router'\n` + routesFile + routesFile = routesFile.replace( + 'const routes = [', + 'const routes: RouteConfig[] = [' + ) + fs.writeFileSync(routesFilePath, routesFile) + } catch (e) { + console.log('Could not add types to routes.ts') + } + + try { + const routerFilePath = api.resolve.app('./src/router/index.ts') + let routerFile = fs.readFileSync(routerFilePath, 'utf8') + routerFile = routerFile.replace( + 'scrollBehavior: () => ({ y: 0 }),', + 'scrollBehavior: () => ({ y: 0, x: 0 }),' + ) + fs.writeFileSync(routerFilePath, routerFile) + } catch (e) { + console.log('Could not add types to index.ts') + } +} + +module.exports = async (api) => { + api.compatibleWith('quasar', '>=1.0.0') + api.compatibleWith('@quasar/app', '>=1.0.0') + + api.render('./templates/base', { + useClassComponentStyle: api.prompts.componentStyle === 'class', + withPrettier: api.prompts.prettier, + }) + + extendPackageJson(api) + + if (api.prompts.vscode) { + vsCodeConfiguration(api) + } + + if (api.prompts.prettier) { + api.render('./templates/prettier') + } + + if (api.prompts.componentStyle === 'composition') { + api.render(`./templates/composition-api`) + } else if (api.prompts.componentStyle === 'class') { + api.render(`./templates/class-api`) + } + + if (api.prompts.codeUpdate) { + await updateCode(api) + } + + // We are sure @quasar/app is there because the AE system works only when it is present + const nodePackager = require('@quasar/app/lib/helpers/node-packager') + + console.log('Installing dependencies...') + await execa(nodePackager, 'install') + + // We'll always get some lint errors until we switch all files to ES6 syntax + // or programmatically add `eslint-env node` around and `eslint-disable-next-line` for + // problematic points + try { + console.log('Running linter...') + await execa( + nodePackager, + (nodePackager === 'npm' ? ['run'] : []).concat(['lint', '--fix']) + ) + console.log('Linter fixed all possible problems') + } catch (e) { + console.log( + "Linter found some errors which wasn'n able to automatically fix, please fix them manually" + ) + } + + // TODO: for some reason, calling quasar cli into here prevents render commands to take place + // console.log( + // "@quasar/typescript AE will now uninstall its dependency because it's no longer needed" + // ) + // await execa('quasar', ['ext', 'remove', '@quasar/typescript']) + // console.log('@quasar/typescript AE succesfully uninstalled its dependency!') } diff --git a/extension/src/prompts.js b/extension/src/prompts.js index 93b553d..1423576 100644 --- a/extension/src/prompts.js +++ b/extension/src/prompts.js @@ -10,33 +10,40 @@ module.exports = function() { return [ { - name: 'webpack', + name: 'codeUpdate', + type: 'confirm', + required: true, + message: + "Update code where possible (use this only if you're using code versioning and will be able to rollback)" + }, + { + name: 'componentStyle', type: 'list', required: true, - message: 'Please choose how to derive webpack:', + message: 'Pick a component style:', choices: [ { name: - 'Use the fork-ts-checker-webpack-plugin module for type-checking (recommended)', - value: 'plugin' + 'Composition API (recommended) (https://github.com/vuejs/composition-api)', + value: 'composition' }, { - name: 'Use vanilla ts-loader', - value: 'vanilla' + name: + 'Class-based (recommended) (https://github.com/vuejs/vue-class-component & https://github.com/kaorun343/vue-property-decorator)', + value: 'class' + }, + { + name: 'None (keep old Object API)', + value: 'object' } ] }, - { - name: 'rename', - type: 'confirm', - required: true, - message: 'Rename .js files to .ts (experimental)' - }, { name: 'vscode', type: 'confirm', required: true, - message: 'Will you use VSCode for this project? (Adds ESLint and Vetur configuration quirks, you must manually install the extensions)' + message: + 'Will you use VSCode for this project? (Adds ESLint and Vetur configuration quirks, you must manually install the extensions)' }, { name: 'prettier', diff --git a/extension/src/templates/base/_eslintrc.js b/extension/src/templates/base/_eslintrc.js new file mode 100644 index 0000000..9790916 --- /dev/null +++ b/extension/src/templates/base/_eslintrc.js @@ -0,0 +1,91 @@ +const { resolve } = require('path') + +module.exports = { + // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy + // This option interrupts the configuration hierarchy at this file + // Remove this if you have an higher level ESLint config file (it usually happens into a monorepo) + root: true, + + // https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser + // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working + // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted + parserOptions: { + // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration + // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint + // Needed to make the parser take into account 'vue' files + extraFileExtensions: ['.vue'], + parser: '@typescript-eslint/parser', + project: resolve(__dirname, './tsconfig.json'), + tsconfigRootDir: __dirname, + ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features + sourceType: 'module' // Allows for the use of imports + }, + + // Configuration extensions order is important + // CHANGE IT ONLY IF YOU KNOW WHAT YOU'RE DOING + extends: [ + // Base ESLint recommended rules + // 'eslint:recommended', + + // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage + // ESLint typescript rules, uncomment when using 'eslint:recommended' + // 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + // consider disabling this class of rules if linting takes too long + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + + // Uncomment any of the lines below to choose desired strictness, + // but leave only one uncommented! + // See https://eslint.vuejs.org/rules/#available-rules + 'plugin:vue/essential', // Priority A: Essential (Error Prevention) + // 'plugin:vue/strongly-recommended' // Priority B: Strongly Recommended (Improving Readability) + // 'plugin:vue/recommended' // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) + + <% if (withPrettier) { %> + // https://github.com/prettier/eslint-config-prettier#installation + // usage with Prettier, provided by 'eslint-config-prettier'. + 'prettier', + 'prettier/@typescript-eslint', + 'prettier/vue'<% } %> + ], + + plugins: [ + // required to apply rules which need type information + '@typescript-eslint', + + // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file + // required to lint *.vue files + 'vue', + + <% if (withPrettier) { %> + // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 + // Prettier has not been included as plugin to avoid performance impact + // add it as an extension for your IDE + <% } %> + ], + + env: { + browser: true + }, + + globals: { + 'ga': true, // Google Analytics + 'cordova': true, + '__statics': true, + 'process': true, + 'Capacitor': true, + 'chrome': true + }, + + // add your custom rules here + rules: { + 'prefer-promise-reject-errors': 'off', + + // TypeScript + 'quotes': ['warn', 'single'], + '@typescript-eslint/explicit-function-return-type': 'off', + + // allow debugger during development only + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' + } +} diff --git a/extension/src/templates/base/components/models.ts b/extension/src/templates/base/components/models.ts new file mode 100644 index 0000000..6945920 --- /dev/null +++ b/extension/src/templates/base/components/models.ts @@ -0,0 +1,8 @@ +export interface Todo { + id: number; + content: string; +} + +export interface Meta { + totalCount: number; +} diff --git a/extension/src/templates/base/tsconfig.json b/extension/src/templates/base/tsconfig.json index 9f52ac9..8e74094 100644 --- a/extension/src/templates/base/tsconfig.json +++ b/extension/src/templates/base/tsconfig.json @@ -1,16 +1,8 @@ { + "extends": "@quasar/app/tsconfig-preset", "compilerOptions": { - "allowJs": true, - "sourceMap": true, - "target": "es6", - "strict": true, + "baseUrl": "."<% if (useClassComponentStyle) { %> "experimentalDecorators": true, - "module": "esnext", - "moduleResolution": "node", - "baseUrl": ".", - "types": [ - "quasar" - ] - }, - "exclude": ["node_modules"] -} + <% } %> + } +} \ No newline at end of file diff --git a/extension/src/templates/class-api/components/ClassComponent.vue b/extension/src/templates/class-api/components/ClassComponent.vue new file mode 100644 index 0000000..7813f3b --- /dev/null +++ b/extension/src/templates/class-api/components/ClassComponent.vue @@ -0,0 +1,40 @@ + + + diff --git a/extension/src/templates/composition-api/components/CompositionComponent.vue b/extension/src/templates/composition-api/components/CompositionComponent.vue new file mode 100644 index 0000000..3187269 --- /dev/null +++ b/extension/src/templates/composition-api/components/CompositionComponent.vue @@ -0,0 +1,61 @@ + + + diff --git a/extension/src/templates/noprettier/_eslintrc.js b/extension/src/templates/noprettier/_eslintrc.js deleted file mode 100644 index 9e123a7..0000000 --- a/extension/src/templates/noprettier/_eslintrc.js +++ /dev/null @@ -1,70 +0,0 @@ -module.exports = { - root: true, - - // Rules order is important, please avoid shuffling them - extends: [ - // Base ESLint recommended rules - 'eslint:recommended', - - // ESLint typescript rules - // See https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - - // `plugin:vue/essential` by default, consider switching to `plugin:vue/strongly-recommended` - // or `plugin:vue/recommended` for stricter rules. - // See https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention - 'plugin:vue/essential' - ], - - plugins: [ - // Required to apply rules which need type information - '@typescript-eslint', - // Required to lint *.vue files - // See https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file - 'vue' - // Prettier has not been included as plugin to avoid performance impact - // See https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 - // Add it as an extension - ], - - // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working - // See https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser - // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted - parserOptions: { - parser: '@typescript-eslint/parser', - sourceType: 'module', - project: './tsconfig.json' - }, - - env: { - browser: true - }, - - globals: { - ga: true, // Google Analytics - cordova: true, - __statics: true, - process: true - }, - - // add your custom rules here - rules: { - 'prefer-promise-reject-errors': 'off', - quotes: ['warn', 'single'], - '@typescript-eslint/indent': ['warn', 2], - - // allow console.log during development only - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - // allow debugger during development only - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - - // Custom - 'vue/component-name-in-template-casing': ['error', 'kebab-case'], - - // Correct typescript linting until at least 2.0.0 major release - // See https://github.com/typescript-eslint/typescript-eslint/issues/501 - // See https://github.com/typescript-eslint/typescript-eslint/issues/493 - '@typescript-eslint/explicit-function-return-type': 'off' - } -} diff --git a/extension/src/templates/prettier/_eslintrc.js b/extension/src/templates/prettier/_eslintrc.js deleted file mode 100644 index 54b4ba9..0000000 --- a/extension/src/templates/prettier/_eslintrc.js +++ /dev/null @@ -1,76 +0,0 @@ -module.exports = { - root: true, - - // Rules order is important, please avoid shuffling them - extends: [ - // Base ESLint recommended rules - 'eslint:recommended', - - // ESLint typescript rules - // See https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - - // `plugin:vue/essential` by default, consider switching to `plugin:vue/strongly-recommended` - // or `plugin:vue/recommended` for stricter rules. - // See https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention - 'plugin:vue/essential', - - // Usage with Prettier, provided by 'eslint-config-prettier'. - // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage-with-prettier - 'prettier', - 'prettier/@typescript-eslint', - 'prettier/vue' - ], - - plugins: [ - // Required to apply rules which need type information - '@typescript-eslint', - // Required to lint *.vue files - // See https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file - 'vue' - // Prettier has not been included as plugin to avoid performance impact - // See https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 - // Add it as an extension - ], - - // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working - // See https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser - // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted - parserOptions: { - parser: '@typescript-eslint/parser', - sourceType: 'module', - project: './tsconfig.json' - }, - - env: { - browser: true - }, - - globals: { - ga: true, // Google Analytics - cordova: true, - __statics: true, - process: true - }, - - // add your custom rules here - rules: { - 'prefer-promise-reject-errors': 'off', - quotes: ['warn', 'single'], - '@typescript-eslint/indent': ['warn', 2], - - // allow console.log during development only - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - // allow debugger during development only - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - - // Custom - 'vue/component-name-in-template-casing': ['error', 'kebab-case'], - - // Correct typescript linting until at least 2.0.0 major release - // See https://github.com/typescript-eslint/typescript-eslint/issues/501 - // See https://github.com/typescript-eslint/typescript-eslint/issues/493 - '@typescript-eslint/explicit-function-return-type': 'off' - } -}