├── .npmignore
├── templates
├── app
│ ├── store
│ │ ├── state.js
│ │ ├── actions.js
│ │ ├── getters.js
│ │ ├── mutations.js
│ │ └── index.js
│ ├── plugin.js
│ ├── page.vue
│ ├── component.vue
│ ├── app.styl
│ ├── babelrc
│ ├── variables.styl
│ └── layout.vue
├── electron
│ ├── icons
│ │ ├── icon.ico
│ │ ├── icon.icns
│ │ └── linux-512x512.png
│ └── main-process
│ │ ├── electron-main.dev.js
│ │ └── electron-main.js
├── pwa
│ ├── custom-service-worker.js
│ └── register-service-worker.js
├── ssr
│ ├── extension.js
│ └── index.js
└── entry
│ ├── import-quasar.js
│ ├── app.js
│ ├── client-prefetch.js
│ ├── server-entry.js
│ └── client-entry.js
├── .gitignore
├── assets
└── logo.art
├── lib
├── helpers
│ ├── is-minimal-terminal.js
│ ├── animations.js
│ ├── on-shutdown.js
│ ├── logger.js
│ ├── cli-error-handling.js
│ ├── get-external-ip.js
│ ├── node-packager.js
│ ├── ensure-deps.js
│ ├── spawn.js
│ ├── net.js
│ ├── ensure-argv.js
│ └── banner.js
├── webpack
│ ├── cordova
│ │ └── index.js
│ ├── inject.hot-update.js
│ ├── spa
│ │ └── index.js
│ ├── pwa
│ │ ├── plugin.pwa-manifest.js
│ │ ├── plugin.html-pwa.js
│ │ └── index.js
│ ├── ssr
│ │ ├── client.js
│ │ ├── server.js
│ │ ├── template.ssr.js
│ │ └── plugin.ssr-prod-artifacts.js
│ ├── inject.preload.js
│ ├── inject.client-specifics.js
│ ├── electron
│ │ ├── plugin.electron-package-json.js
│ │ ├── renderer.js
│ │ └── main.js
│ ├── inject.html.js
│ ├── plugin.html-addons.js
│ ├── inject.style-rules.js
│ ├── plugin.progress.js
│ ├── index.js
│ └── create-chain.js
├── mode
│ ├── index.js
│ ├── install-missing.js
│ ├── mode-pwa.js
│ ├── mode-ssr.js
│ ├── mode-electron.js
│ └── mode-cordova.js
├── node-version-check.js
├── app-paths.js
├── artifacts.js
├── cordova
│ ├── index.js
│ └── cordova-config.js
├── ssr
│ └── html-template.js
├── generator.js
├── electron
│ ├── bundler.js
│ └── index.js
├── legacy-validations.js
├── dev-server.js
└── quasar-config.js
├── .editorconfig
├── bin
├── quasar-clean
├── quasar-help
├── quasar
├── quasar-mode
├── quasar-init
├── quasar-info
├── quasar-new
├── quasar-build
├── quasar-dev
└── quasar-serve
├── lists
└── app-templates.json
├── LICENSE
├── .github
├── CODE_OF_CONDUCT.md
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE.md
└── CONTRIBUTING.md
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .editorconfig
2 | .github
3 |
--------------------------------------------------------------------------------
/templates/app/store/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | //
3 | }
4 |
--------------------------------------------------------------------------------
/templates/app/store/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | export function someAction (context) {
3 | }
4 | */
5 |
--------------------------------------------------------------------------------
/templates/app/store/getters.js:
--------------------------------------------------------------------------------
1 | /*
2 | export function someGetter (state) {
3 | }
4 | */
5 |
--------------------------------------------------------------------------------
/templates/app/store/mutations.js:
--------------------------------------------------------------------------------
1 | /*
2 | export function someMutation (state) {
3 | }
4 | */
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .Thumbs.db
3 | node_modules/
4 | ssl-server.pem
5 | npm-debug.log
6 | *.sublime*
7 |
--------------------------------------------------------------------------------
/templates/electron/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/HEAD/templates/electron/icons/icon.ico
--------------------------------------------------------------------------------
/templates/electron/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/HEAD/templates/electron/icons/icon.icns
--------------------------------------------------------------------------------
/templates/electron/icons/linux-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/HEAD/templates/electron/icons/linux-512x512.png
--------------------------------------------------------------------------------
/templates/app/plugin.js:
--------------------------------------------------------------------------------
1 | // import something here
2 |
3 | // leave the export, even if you don't use it
4 | export default ({ app, router, Vue }) => {
5 | // something to do
6 | }
7 |
--------------------------------------------------------------------------------
/assets/logo.art:
--------------------------------------------------------------------------------
1 | ___
2 | / _ \ _ _ __ _ ___ __ _ _ __
3 | | | | | | | |/ _` / __|/ _` | '__|
4 | | |_| | |_| | (_| \__ \ (_| | |
5 | \__\_\\__,_|\__,_|___/\__,_|_|
6 |
7 |
--------------------------------------------------------------------------------
/templates/pwa/custom-service-worker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file (which will be your service worker)
3 | * is picked up by the build system ONLY if
4 | * quasar.conf > pwa > workboxPluginMode is set to "InjectManifest"
5 | */
6 |
--------------------------------------------------------------------------------
/lib/helpers/is-minimal-terminal.js:
--------------------------------------------------------------------------------
1 | const ci = require('ci-info')
2 |
3 | const isMinimal = (
4 | ci.isCI ||
5 | process.env.NODE_ENV === 'test' ||
6 | !process.stdout.isTTY
7 | )
8 |
9 | module.exports = isMinimal
10 |
--------------------------------------------------------------------------------
/templates/app/page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/lib/helpers/animations.js:
--------------------------------------------------------------------------------
1 | const {
2 | generalAnimations,
3 | inAnimations,
4 | outAnimations
5 | } = require('quasar-extras/animate/animate-list.common')
6 |
7 | module.exports = generalAnimations.concat(inAnimations).concat(outAnimations)
8 |
--------------------------------------------------------------------------------
/templates/app/component.vue:
--------------------------------------------------------------------------------
1 |
2 | My component
3 |
4 |
5 |
13 |
14 |
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/templates/app/store/index.js:
--------------------------------------------------------------------------------
1 | import state from './state'
2 | import * as getters from './getters'
3 | import * as mutations from './mutations'
4 | import * as actions from './actions'
5 |
6 | export default {
7 | namespaced: true,
8 | state,
9 | getters,
10 | mutations,
11 | actions
12 | }
13 |
--------------------------------------------------------------------------------
/lib/webpack/cordova/index.js:
--------------------------------------------------------------------------------
1 | const
2 | injectHtml = require('../inject.html'),
3 | injectClientSpecifics = require('../inject.client-specifics'),
4 | injectHotUpdate = require('../inject.hot-update')
5 |
6 | module.exports = function (chain, cfg) {
7 | injectHtml(chain, cfg)
8 | injectClientSpecifics(chain, cfg)
9 | injectHotUpdate(chain, cfg)
10 | }
11 |
--------------------------------------------------------------------------------
/lib/mode/index.js:
--------------------------------------------------------------------------------
1 | const warn = require('../helpers/logger')('app:quasar-mode', 'red')
2 |
3 | module.exports = function (mode) {
4 | if (!['pwa', 'cordova', 'electron', 'ssr'].includes(mode)) {
5 | warn(`⚠️ Unknown mode specified: ${mode}`)
6 | process.exit(1)
7 | }
8 |
9 | const QuasarMode = require(`./mode-${mode}`)
10 | return new QuasarMode()
11 | }
12 |
--------------------------------------------------------------------------------
/lib/webpack/inject.hot-update.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 |
3 | module.exports = function (chain, cfg) {
4 | if (cfg.ctx.dev && cfg.devServer.hot) {
5 | chain.optimization
6 | .namedModules(true) // HMR shows filenames in console on update
7 |
8 | chain.plugin('hot-module-replacement')
9 | .use(webpack.HotModuleReplacementPlugin)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/templates/app/app.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS FILE IS GENERATED AUTOMATICALLY.
3 | * DO NOT EDIT.
4 | *
5 | * Edit /src/css/themes instead
6 | **/
7 |
8 | // First we load app's Stylus variables
9 | @import '~quasar-app-variables'
10 |
11 | // Then we load Quasar stylus files
12 | @import '~quasar-styl'
13 |
14 | // We add Quasar addons, if they were requested
15 | @import '~quasar-addon-styl'
16 |
--------------------------------------------------------------------------------
/lib/webpack/spa/index.js:
--------------------------------------------------------------------------------
1 | const
2 | injectHtml = require('../inject.html'),
3 | injectClientSpecifics = require('../inject.client-specifics'),
4 | injectHotUpdate = require('../inject.hot-update'),
5 | injectPreload = require('../inject.preload')
6 |
7 | module.exports = function (chain, cfg) {
8 | injectHtml(chain, cfg)
9 | injectClientSpecifics(chain, cfg)
10 | injectHotUpdate(chain, cfg)
11 | injectPreload(chain, cfg)
12 | }
13 |
--------------------------------------------------------------------------------
/lib/helpers/on-shutdown.js:
--------------------------------------------------------------------------------
1 | const log = require('./logger')('app:on-shutdown')
2 |
3 | module.exports = function (fn, msg) {
4 | const cleanup = () => {
5 | try {
6 | msg && log(msg)
7 | fn()
8 | }
9 | finally {
10 | process.exit()
11 | }
12 | }
13 |
14 | process.on('exit', cleanup)
15 | process.on('SIGINT', cleanup)
16 | process.on('SIGTERM', cleanup)
17 | process.on('SIGHUP', cleanup)
18 | process.on('SIGBREAK', cleanup)
19 | }
20 |
--------------------------------------------------------------------------------
/lib/webpack/pwa/plugin.pwa-manifest.js:
--------------------------------------------------------------------------------
1 | module.exports = class PwaManifest {
2 | constructor (cfg = {}) {
3 | this.manifest = JSON.stringify(cfg.pwa.manifest)
4 | }
5 |
6 | apply (compiler) {
7 | compiler.hooks.emit.tapAsync('manifest.json', (compiler, callback) => {
8 | compiler.assets['manifest.json'] = {
9 | source: () => new Buffer(this.manifest),
10 | size: () => Buffer.byteLength(this.manifest)
11 | }
12 |
13 | callback()
14 | })
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/webpack/ssr/client.js:
--------------------------------------------------------------------------------
1 | const
2 | injectClientSpecifics = require('../inject.client-specifics'),
3 | injectHotUpdate = require('../inject.hot-update'),
4 | VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
5 |
6 | module.exports = function (chain, cfg) {
7 | injectClientSpecifics(chain, cfg)
8 | injectHotUpdate(chain, cfg)
9 |
10 | chain.plugin('vue-ssr-client')
11 | .use(VueSSRClientPlugin, [{
12 | filename: '../vue-ssr-client-manifest.json'
13 | }])
14 | }
15 |
--------------------------------------------------------------------------------
/lib/helpers/logger.js:
--------------------------------------------------------------------------------
1 | const
2 | ms = require('ms'),
3 | chalk = require('chalk')
4 |
5 | let prevTime
6 |
7 | module.exports = function (banner, color = 'green') {
8 | return function (msg) {
9 | const
10 | curr = +new Date(),
11 | diff = curr - (prevTime || curr)
12 |
13 | prevTime = curr
14 |
15 | if (msg) {
16 | console.log(` ${chalk[color](banner)} ${msg} ${chalk.green(`+${ms(diff)}`)}`)
17 | }
18 | else {
19 | console.log()
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/mode/install-missing.js:
--------------------------------------------------------------------------------
1 | const
2 | logger = require('../helpers/logger'),
3 | log = logger('app:mode'),
4 | warn = logger('app:mode', 'red'),
5 | getMode = require('./index')
6 |
7 | module.exports = function (mode, target) {
8 | const Mode = getMode(mode)
9 |
10 | if (Mode.isInstalled) {
11 | if (mode === 'cordova') {
12 | Mode.addPlatform(target)
13 | }
14 | return
15 | }
16 |
17 | warn(`Quasar ${mode.toUpperCase()} is missing. Installing it...`)
18 | Mode.add(target)
19 | }
20 |
--------------------------------------------------------------------------------
/lib/webpack/inject.preload.js:
--------------------------------------------------------------------------------
1 | module.exports = function (chain, cfg) {
2 | if (cfg.ctx.prod && cfg.build.preloadChunks) {
3 | const PreloadPlugin = require('preload-webpack-plugin')
4 |
5 | chain.plugin('preload')
6 | .use(PreloadPlugin, [{
7 | rel: 'preload',
8 | include: 'initial',
9 | fileBlacklist: [/\.map$/, /hot-update\.js$/]
10 | }])
11 | chain.plugin('prefetch')
12 | .use(PreloadPlugin, [{
13 | rel: 'prefetch',
14 | include: 'asyncChunks'
15 | }])
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/helpers/cli-error-handling.js:
--------------------------------------------------------------------------------
1 | const pe = require('pretty-error').start()
2 |
3 | pe.skipPackage('regenerator-runtime')
4 | pe.skipPackage('babel-runtime')
5 | pe.skipNodeFiles()
6 |
7 | let ouchInstance
8 |
9 | module.exports.getOuchInstance = function () {
10 | if (ouchInstance) {
11 | return ouchInstance
12 | }
13 |
14 | pe.stop()
15 |
16 | const Ouch = require('ouch')
17 | ouchInstance = (new Ouch()).pushHandler(
18 | new Ouch.handlers.PrettyPageHandler('orange', null, 'sublime')
19 | )
20 |
21 | return ouchInstance
22 | }
23 |
--------------------------------------------------------------------------------
/lib/node-version-check.js:
--------------------------------------------------------------------------------
1 | const
2 | version = process.version.split('.'),
3 | major = parseInt(version[0].replace(/\D/g,''), 10)
4 | minor = parseInt(version[1].replace(/\D/g,''), 10)
5 |
6 | if (major < 8 || (major === 8 && minor < 9)) {
7 | console.warn('\x1b[41m%s\x1b[0m', 'INCOMPATIBLE NODE VERSION')
8 | console.warn('\x1b[33m%s\x1b[0m', 'Quasar CLI requires Node 8.9.0 or superior')
9 | console.warn('')
10 | console.warn('⚠️ You are running Node ' + version)
11 | console.warn('Please install a compatible Node version and try again')
12 |
13 | process.exit(1)
14 | }
15 |
--------------------------------------------------------------------------------
/templates/app/babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env", {
5 | "modules": false,
6 | "loose": false,
7 | "useBuiltIns": "usage"
8 | }
9 | ],
10 | [
11 | "@babel/preset-stage-2", {
12 | "modules": false,
13 | "loose": false,
14 | "useBuiltIns": true,
15 | "decoratorsLegacy": true
16 | }
17 | ]
18 | ],
19 | "plugins": [
20 | [
21 | "@babel/transform-runtime", {
22 | "polyfill": false,
23 | "regenerator": false
24 | }
25 | ]
26 | ],
27 | "comments": false
28 | }
29 |
--------------------------------------------------------------------------------
/lib/webpack/inject.client-specifics.js:
--------------------------------------------------------------------------------
1 | module.exports = function (chain) {
2 | chain.node
3 | .merge({
4 | // prevent webpack from injecting useless setImmediate polyfill because Vue
5 | // source contains it (although only uses it if it's native).
6 | setImmediate: false,
7 | // process is injected via DefinePlugin, although some 3rd party
8 | // libraries may require a mock to work properly (#934)
9 | process: 'mock',
10 | // prevent webpack from injecting mocks to Node native modules
11 | // that does not make sense for the client
12 | dgram: 'empty',
13 | fs: 'empty',
14 | net: 'empty',
15 | tls: 'empty',
16 | child_process: 'empty'
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/lib/webpack/electron/plugin.electron-package-json.js:
--------------------------------------------------------------------------------
1 | const
2 | appPaths = require('../../app-paths')
3 |
4 | module.exports = class ElectronPackageJson {
5 | apply (compiler) {
6 | compiler.hooks.emit.tapAsync('package.json', (compiler, callback) => {
7 | const pkg = require(appPaths.resolve.app('package.json'))
8 |
9 | // we don't need this (also, faster install time & smaller bundles)
10 | delete pkg.devDependencies
11 |
12 | pkg.main = './electron-main.js'
13 | const source = JSON.stringify(pkg)
14 |
15 | compiler.assets['package.json'] = {
16 | source: () => new Buffer(source),
17 | size: () => Buffer.byteLength(source)
18 | }
19 |
20 | callback()
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/bin/quasar-clean:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const
4 | parseArgs = require('minimist'),
5 | path = require('path'),
6 | chalk = require('chalk')
7 |
8 | const
9 | appPaths = require('../lib/app-paths'),
10 | log = require('../lib/helpers/logger')('app:clean')
11 |
12 | const argv = parseArgs(process.argv.slice(2), {
13 | alias: {
14 | h: 'help'
15 | },
16 | boolean: ['h']
17 | })
18 |
19 | if (argv.help) {
20 | console.log(`
21 | Description
22 | Cleans all build artifacts
23 | Usage
24 | $ quasar clean
25 | Options
26 | --help, -h Displays this message
27 | `)
28 | process.exit(0)
29 | }
30 |
31 | require('../lib/artifacts').cleanAll()
32 |
33 | console.log()
34 | log(`Done cleaning build artifacts`)
35 | log()
36 |
--------------------------------------------------------------------------------
/templates/electron/main-process/electron-main.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is used specifically and only for development. It installs
3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to
4 | * modify this file, but it can be used to extend your development
5 | * environment.
6 | */
7 |
8 | // Install `electron-debug` with `devtron`
9 | require('electron-debug')({ showDevTools: true })
10 |
11 | // Install `vue-devtools`
12 | require('electron').app.on('ready', () => {
13 | let installExtension = require('electron-devtools-installer')
14 | installExtension.default(installExtension.VUEJS_DEVTOOLS)
15 | .then(() => {})
16 | .catch(err => {
17 | console.log('Unable to install `vue-devtools`: \n', err)
18 | })
19 | })
20 |
21 | // Require `main` process to boot app
22 | require('./electron-main')
23 |
--------------------------------------------------------------------------------
/lib/helpers/get-external-ip.js:
--------------------------------------------------------------------------------
1 | const warn = require('./logger')('app:external-ip')
2 |
3 | module.exports = async function () {
4 | const interfaces = await require('./net').getExternalNetworkInterface()
5 |
6 | if (interfaces.length === 0) {
7 | warn(`⚠️ No external IP detected. Can't run without one. Manually specify one?`)
8 | warn()
9 | process.exit(1)
10 | }
11 |
12 | if (interfaces.length === 1) {
13 | const address = interfaces[0].address
14 | warn(`Detected external IP ${address} and using it`)
15 | return address
16 | }
17 |
18 | const answer = await require('inquirer').prompt([{
19 | type: 'list',
20 | name: 'address',
21 | message: 'What external IP should Quasar use?',
22 | choices: interfaces.map(interface => interface.address)
23 | }])
24 |
25 | return answer.address
26 | }
27 |
--------------------------------------------------------------------------------
/lib/webpack/electron/renderer.js:
--------------------------------------------------------------------------------
1 | const
2 | appPaths = require('../../app-paths'),
3 | injectHtml = require('../inject.html'),
4 | injectClientSpecifics = require('../inject.client-specifics'),
5 | injectHotUpdate = require('../inject.hot-update')
6 |
7 | module.exports = function (chain, cfg) {
8 | if (cfg.ctx.build) {
9 | chain.output
10 | .libraryTarget('commonjs2')
11 | }
12 |
13 | chain.node
14 | .merge({
15 | __dirname: cfg.ctx.dev,
16 | __filename: cfg.ctx.dev
17 | })
18 |
19 | chain.resolve.extensions
20 | .add('.node')
21 |
22 | chain.target('electron-renderer')
23 |
24 | chain.module.rule('node')
25 | .test(/\.node$/)
26 | .use('node-loader')
27 | .loader('node-loader')
28 |
29 | injectHtml(chain, cfg)
30 | injectClientSpecifics(chain, cfg)
31 | injectHotUpdate(chain, cfg)
32 | }
33 |
--------------------------------------------------------------------------------
/templates/app/variables.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS FILE IS GENERATED AUTOMATICALLY.
3 | * DO NOT EDIT.
4 | *
5 | * Edit /src/css/themes instead
6 | **/
7 |
8 | // Webpack alias "variables" points to this file.
9 | // So you can import it in your app's *.vue files
10 | // inside the
18 |
19 | // First we load app's Stylus variables
20 | @import '~quasar-app-variables'
21 |
22 | // Then we load Quasar Stylus variables.
23 | // Any variables defined in "app.variables.styl"
24 | // will override Quasar's ones.
25 | //
26 | // NOTICE that we only import Core Quasar Variables
27 | // like colors, media breakpoints, and so.
28 | // No component variable will be included.
29 | @import '~quasar-framework/src/css/core.variables'
30 |
--------------------------------------------------------------------------------
/lib/webpack/inject.html.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const
4 | appPaths = require('../app-paths'),
5 | HtmlWebpackPlugin = require('html-webpack-plugin'),
6 | HtmlAddonsPlugin = require('./plugin.html-addons').plugin
7 |
8 | module.exports = function (chain, cfg) {
9 | chain.plugin('html-webpack')
10 | .use(HtmlWebpackPlugin, [
11 | Object.assign({}, cfg.__html.variables, {
12 | filename: cfg.ctx.dev
13 | ? 'index.html'
14 | : path.join(cfg.build.distDir, cfg.build.htmlFilename),
15 | template: appPaths.resolve.app(cfg.sourceFiles.indexHtmlTemplate),
16 | minify: cfg.__html.minifyOptions,
17 |
18 | chunksSortMode: 'none',
19 | // inject script tags for bundle
20 | inject: true,
21 | cache: true
22 | })
23 | ])
24 |
25 | chain.plugin('html-addons')
26 | .use(HtmlAddonsPlugin, [ cfg ])
27 | }
28 |
--------------------------------------------------------------------------------
/templates/ssr/extension.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only
3 | * the ES6 features that are supported by your Node version. https://node.green/
4 | *
5 | * All content of this folder will be copied as is to the output folder. So only import:
6 | * 1. node_modules (and yarn/npm install dependencies -- NOT to devDependecies though)
7 | * 2. create files in this folder and import only those with the relative path
8 | *
9 | * Note: This file is used for both PRODUCTION & DEVELOPMENT.
10 | * Note: Changes to this file (but not any file it imports!) are picked up by the
11 | * development server, but such updates are costly since the dev-server needs a reboot.
12 | */
13 |
14 | module.exports.extendApp = function ({ app }) {
15 | /*
16 | Extend the parts of the express app that you
17 | want to use with development server too.
18 |
19 | Example: app.use(), app.get() etc
20 | */
21 | }
22 |
--------------------------------------------------------------------------------
/lib/mode/mode-pwa.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | fse = require('fs-extra'),
4 | appPaths = require('../app-paths'),
5 | logger = require('../helpers/logger'),
6 | log = logger('app:mode-pwa'),
7 | warn = logger('app:mode-pwa', 'red')
8 |
9 | class Mode {
10 | get isInstalled () {
11 | return fs.existsSync(appPaths.pwaDir)
12 | }
13 |
14 | add (params) {
15 | if (this.isInstalled) {
16 | warn(`PWA support detected already. Aborting.`)
17 | return
18 | }
19 |
20 | log(`Creating PWA source folder...`)
21 | fse.copySync(appPaths.resolve.cli('templates/pwa'), appPaths.pwaDir)
22 | log(`PWA support was added`)
23 | }
24 |
25 | remove () {
26 | if (!this.isInstalled) {
27 | warn(`No PWA support detected. Aborting.`)
28 | return
29 | }
30 |
31 | log(`Removing PWA source folder`)
32 | fse.removeSync(appPaths.pwaDir)
33 | log(`PWA support was removed`)
34 | }
35 | }
36 |
37 | module.exports = Mode
38 |
--------------------------------------------------------------------------------
/lib/mode/mode-ssr.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | fse = require('fs-extra'),
4 | appPaths = require('../app-paths'),
5 | logger = require('../helpers/logger'),
6 | log = logger('app:mode-ssr'),
7 | warn = logger('app:mode-ssr', 'red')
8 |
9 | class Mode {
10 | get isInstalled () {
11 | return fs.existsSync(appPaths.ssrDir)
12 | }
13 |
14 | add (params) {
15 | if (this.isInstalled) {
16 | warn(`SSR support detected already. Aborting.`)
17 | return
18 | }
19 |
20 | log(`Creating SSR source folder...`)
21 | fse.copySync(appPaths.resolve.cli('templates/ssr'), appPaths.ssrDir)
22 | log(`SSR support was added`)
23 | }
24 |
25 | remove () {
26 | if (!this.isInstalled) {
27 | warn(`No SSR support detected. Aborting.`)
28 | return
29 | }
30 |
31 | log(`Removing SSR source folder`)
32 | fse.removeSync(appPaths.ssrDir)
33 | log(`SSR support was removed`)
34 | }
35 | }
36 |
37 | module.exports = Mode
38 |
--------------------------------------------------------------------------------
/bin/quasar-help:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console.log()
4 | console.log(
5 | require('fs').readFileSync(
6 | require('path').join(__dirname, '../assets/logo.art'),
7 | 'utf8'
8 | )
9 | )
10 |
11 | console.log(`
12 | Example usage
13 | $ quasar
14 |
15 | Help for a command
16 | $ quasar --help
17 | $ quasar -h
18 |
19 | Options
20 | --version, -v Print Quasar CLI version
21 |
22 | Commands
23 | init Create a project folder
24 | dev Start a dev server for your App
25 | build Build your app for production
26 | clean Clean all build artifacts
27 | new Quickly scaffold page/layout/component/... vue file
28 | mode Add/remove Quasar Modes for your App
29 | info Display info about your machine and your App
30 | serve Create an ad-hoc server on App's distributables
31 | help Displays this message
32 | `)
33 |
--------------------------------------------------------------------------------
/lists/app-templates.json:
--------------------------------------------------------------------------------
1 | {
2 | "latest": "default",
3 | "official": [
4 | {
5 | "name": "default",
6 | "description": "Boilerplate for website (with optional Cordova and Electron wrappers) with Quasar v0.14+"
7 | },
8 | {
9 | "name": "pwa",
10 | "description": "Boilerplate for PWA with Quasar v0.14+"
11 | },
12 | {
13 | "name": "default#v0.13",
14 | "description": "Boilerplate for Quasar v0.13",
15 | "deprecated": true
16 | },
17 | {
18 | "name": "default#webpack1",
19 | "description": "Boilerplate for Quasar v0.8-v0.12 (Webpack 1, Vue 2)",
20 | "deprecated": true
21 | },
22 | {
23 | "name": "default#vue1",
24 | "description": "Boilerplate for Quasar <= 0.7 (Webpack 1, Vue 1)",
25 | "deprecated": true
26 | }
27 | ],
28 | "community": [
29 | {
30 | "name": "meteor",
31 | "description": "Boilerplate for Quasar v0.9+ with Meteor"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/templates/electron/main-process/electron-main.js:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from 'electron'
2 |
3 | /**
4 | * Set `__statics` path to static files in production;
5 | * The reason we are setting it here is that the path needs to be evaluated at runtime
6 | */
7 | if (process.env.PROD) {
8 | global.__statics = require('path').join(__dirname, 'statics').replace(/\\/g, '\\\\')
9 | }
10 |
11 | let mainWindow
12 |
13 | function createWindow () {
14 | /**
15 | * Initial window options
16 | */
17 | mainWindow = new BrowserWindow({
18 | width: 1000,
19 | height: 600,
20 | useContentSize: true
21 | })
22 |
23 | mainWindow.loadURL(process.env.APP_URL)
24 |
25 | mainWindow.on('closed', () => {
26 | mainWindow = null
27 | })
28 | }
29 |
30 | app.on('ready', createWindow)
31 |
32 | app.on('window-all-closed', () => {
33 | if (process.platform !== 'darwin') {
34 | app.quit()
35 | }
36 | })
37 |
38 | app.on('activate', () => {
39 | if (mainWindow === null) {
40 | createWindow()
41 | }
42 | })
43 |
--------------------------------------------------------------------------------
/lib/helpers/node-packager.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | const
4 | appPaths = require('../app-paths'),
5 | spawn = require('cross-spawn').sync,
6 | warn = require('./logger')('app:node-packager', 'red')
7 |
8 | function isInstalled (cmd) {
9 | try {
10 | return spawn(cmd, ['--version']).status === 0
11 | }
12 | catch (err) {
13 | return false
14 | }
15 | }
16 |
17 | function getPackager () {
18 | if (!fs.existsSync(appPaths.resolve.app('node_modules'))) {
19 | warn('⚠️ Please run "yarn" / "npm install" first')
20 | warn()
21 | process.exit(1)
22 | }
23 |
24 | if (fs.existsSync(appPaths.resolve.app('yarn.lock'))) {
25 | return 'yarn'
26 | }
27 |
28 | if (fs.existsSync(appPaths.resolve.app('package-lock.json'))) {
29 | return 'npm'
30 | }
31 |
32 | if (isInstalled('yarn')) {
33 | return 'yarn'
34 | }
35 |
36 | if (isInstalled('npm')) {
37 | return 'npm'
38 | }
39 |
40 | warn('⚠️ Please install Yarn or NPM before running this command.')
41 | warn()
42 | }
43 |
44 | module.exports = getPackager()
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present Razvan Stoenescu
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/helpers/ensure-deps.js:
--------------------------------------------------------------------------------
1 | const
2 | appPaths = require('../app-paths'),
3 | logger = require('../helpers/logger'),
4 | log = logger('app:ensure-dev-deps'),
5 | warn = logger('app:ensure-dev-deps', 'red'),
6 | spawn = require('./spawn'),
7 | nodePackager = require('./node-packager')
8 |
9 | function needsStripAnsi (pkg) {
10 | if (pkg.devDependencies && pkg.devDependencies['strip-ansi'] && pkg.devDependencies['strip-ansi'].indexOf('3.0.1') > -1) {
11 | return false
12 | }
13 | if (pkg.dependencies && pkg.dependencies['strip-ansi'] && pkg.dependencies['strip-ansi'].indexOf('3.0.1') > -1) {
14 | return false
15 | }
16 |
17 | return true
18 | }
19 |
20 | module.exports = function () {
21 | const pkg = require(appPaths.resolve.app('package.json'))
22 |
23 | if (needsStripAnsi(pkg)) {
24 | const cmdParam = nodePackager === 'npm'
25 | ? ['install', '--save-dev']
26 | : ['add', '--dev']
27 |
28 | cmdParam.push('strip-ansi@=3.0.1')
29 |
30 | log(`Pinning strip-ansi dependency...`)
31 | spawn.sync(
32 | nodePackager,
33 | cmdParam,
34 | appPaths.appDir,
35 | () => warn('Failed to install strip-ansi dependency')
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/templates/pwa/register-service-worker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is picked up by the build system only
3 | * when building for PRODUCTION
4 | */
5 |
6 | import { register } from 'register-service-worker'
7 |
8 | register(process.env.SERVICE_WORKER_FILE, {
9 | ready () {
10 | console.log('App is being served from cache by a service worker.')
11 | },
12 | registered (registration) { // registration -> a ServiceWorkerRegistration instance
13 | console.log('Service worker has been registered.')
14 | },
15 | cached (registration) { // registration -> a ServiceWorkerRegistration instance
16 | console.log('Content has been cached for offline use.')
17 | },
18 | updatefound (registration) { // registration -> a ServiceWorkerRegistration instance
19 | console.log('New content is downloading.')
20 | },
21 | updated (registration) { // registration -> a ServiceWorkerRegistration instance
22 | console.log('New content is available; please refresh.')
23 | },
24 | offline () {
25 | console.log('No internet connection found. App is running in offline mode.')
26 | },
27 | error (err) {
28 | console.error('Error during service worker registration:', err)
29 | }
30 | })
31 |
32 | // ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
33 |
--------------------------------------------------------------------------------
/lib/helpers/spawn.js:
--------------------------------------------------------------------------------
1 | const
2 | logger = require('./logger'),
3 | log = logger('app:spawn'),
4 | warn = logger('app:spawn', 'red'),
5 | spawn = require('cross-spawn')
6 |
7 | /*
8 | Returns pid, takes onClose
9 | */
10 | module.exports = function (cmd, params, cwd, onClose) {
11 | log(`Running "${cmd} ${params.join(' ')}"`)
12 | log()
13 |
14 | const runner = spawn(
15 | cmd,
16 | params,
17 | { stdio: 'inherit', stdout: 'inherit', stderr: 'inherit', cwd }
18 | )
19 |
20 | runner.on('close', code => {
21 | log()
22 | if (code) {
23 | log(`Command "${cmd}" failed with exit code: ${code}`)
24 | }
25 |
26 | onClose && onClose(code)
27 | })
28 |
29 | return runner.pid
30 | }
31 |
32 | /*
33 | Returns nothing, takes onFail
34 | */
35 | module.exports.sync = function (cmd, params, cwd, onFail) {
36 | log(`[sync] Running "${cmd} ${params.join(' ')}"`)
37 | log()
38 |
39 | const runner = spawn.sync(
40 | cmd,
41 | params,
42 | { stdio: 'inherit', stdout: 'inherit', stderr: 'inherit', cwd }
43 | )
44 |
45 | if (runner.status || runner.error) {
46 | warn()
47 | warn(`⚠️ Command "${cmd}" failed with exit code: ${runner.status}`)
48 | if (runner.status === null) {
49 | warn(`⚠️ Please globally install "${cmd}"`)
50 | }
51 | onFail && onFail()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6 |
7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8 |
9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10 |
11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12 |
13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
14 |
--------------------------------------------------------------------------------
/bin/quasar:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const localFile = require('import-local-file')(__filename)
4 | if (localFile) {
5 | require(localFile)
6 | }
7 | else {
8 | require('../lib/node-version-check')
9 | require('../lib/helpers/cli-error-handling')
10 |
11 | const commands = [
12 | 'init',
13 | 'dev',
14 | 'build',
15 | 'clean',
16 | 'mode',
17 | 'info',
18 | 'serve',
19 | 'new',
20 | 'help'
21 | ]
22 |
23 | let cmd = process.argv[2]
24 |
25 | if (cmd) {
26 | if (cmd.length === 1) {
27 | const mapToCmd = {
28 | d: 'dev',
29 | b: 'build',
30 | c: 'clean',
31 | m: 'mode',
32 | i: 'info',
33 | s: 'serve',
34 | n: 'new',
35 | h: 'help'
36 | }
37 | cmd = mapToCmd[cmd]
38 | }
39 |
40 | if (commands.includes(cmd)) {
41 | process.argv.splice(2, 1)
42 | }
43 | else {
44 | if (cmd === '-v' || cmd === '--version') {
45 | console.log(require('../package.json').version)
46 | process.exit(0)
47 | }
48 |
49 | const warn = require('../lib/helpers/logger')('app', 'red')
50 |
51 | warn()
52 | warn(`Unknown command "${ cmd }"`)
53 | if (cmd.indexOf('-') === 0) {
54 | warn(`Command must come before the options`)
55 | }
56 |
57 | warn()
58 | cmd = 'help'
59 | }
60 | }
61 | else {
62 | cmd = 'help'
63 | }
64 |
65 | require(`./quasar-${cmd}`)
66 | }
67 |
--------------------------------------------------------------------------------
/templates/entry/import-quasar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS FILE IS GENERATED AUTOMATICALLY.
3 | * DO NOT EDIT.
4 | *
5 | * You are probably looking on adding initialization code.
6 | * Use "quasar new plugin " and add it there.
7 | * One plugin per concern. Then reference the file(s) in quasar.conf.js > plugins:
8 | * plugins: ['file', ...] // do not add ".js" extension to it.
9 | **/
10 | <%
11 | let useStatement = [ `config: ${JSON.stringify(framework.config)}` ]
12 |
13 | if (framework.i18n) { %>
14 | import lang from 'quasar-framework/i18n/<%= framework.i18n %>'
15 | <%
16 | useStatement.push('i18n: lang')
17 | }
18 |
19 | if (framework.iconSet) { %>
20 | import iconSet from 'quasar-framework/icons/<%= framework.iconSet %>'
21 | <%
22 | useStatement.push('iconSet: iconSet')
23 | }
24 | %>
25 |
26 | import Vue from 'vue'
27 | <% if (framework.all) { %>
28 | import Quasar from 'quasar'
29 | <% } else {
30 | let importStatement = []
31 |
32 | ;['components', 'directives', 'plugins'].forEach(type => {
33 | if (framework[type]) {
34 | let items = framework[type].filter(item => item)
35 | if (items.length > 0) {
36 | useStatement.push(type + ': {' + items.join(',') + '}')
37 | importStatement = importStatement.concat(items)
38 | }
39 | }
40 | })
41 |
42 | importStatement = '{Quasar' + (importStatement.length ? ',' + importStatement.join(',') : '') + '}'
43 | %>
44 | import <%= importStatement %> from 'quasar'
45 | <% } %>
46 |
47 | Vue.use(Quasar, { <%= useStatement.join(',') %> })
48 |
--------------------------------------------------------------------------------
/lib/app-paths.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | resolve = path.resolve,
5 | join = path.join
6 |
7 | function getAppDir () {
8 | let dir = process.cwd()
9 |
10 | while (dir.length && dir[dir.length - 1] !== path.sep) {
11 | if (fs.existsSync(join(dir, 'quasar.conf.js'))) {
12 | return dir
13 | }
14 |
15 | dir = path.normalize(join(dir, '..'))
16 | }
17 |
18 | const
19 | logger = require('./helpers/logger')
20 | warn = logger('app:paths', 'red')
21 |
22 | warn(`⚠️ Error. This command must be executed inside a Quasar v0.15+ project folder.`)
23 | warn(`For Quasar pre v0.15 projects, npm uninstall -g quasar-cli; npm i -g quasar-cli@0.6.5`)
24 | warn()
25 | process.exit(1)
26 | }
27 |
28 | const
29 | appDir = getAppDir(),
30 | cliDir = resolve(__dirname, '..'),
31 | srcDir = resolve(appDir, 'src'),
32 | pwaDir = resolve(appDir, 'src-pwa'),
33 | ssrDir = resolve(appDir, 'src-ssr'),
34 | cordovaDir = resolve(appDir, 'src-cordova'),
35 | electronDir = resolve(appDir, 'src-electron')
36 |
37 | module.exports = {
38 | cliDir,
39 | appDir,
40 | srcDir,
41 | pwaDir,
42 | ssrDir,
43 | cordovaDir,
44 | electronDir,
45 |
46 | resolve: {
47 | cli: dir => join(cliDir, dir),
48 | app: dir => join(appDir, dir),
49 | src: dir => join(srcDir, dir),
50 | pwa: dir => join(pwaDir, dir),
51 | ssr: dir => join(ssrDir, dir),
52 | cordova: dir => join(cordovaDir, dir),
53 | electron: dir => join(electronDir, dir)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | **What kind of change does this PR introduce?** (check at least one)
10 |
11 | - [ ] Bugfix
12 | - [ ] Feature
13 | - [ ] Code style update
14 | - [ ] Refactor
15 | - [ ] Build-related changes
16 | - [ ] Other, please describe:
17 |
18 | **Does this PR introduce a breaking change?** (check one)
19 |
20 | - [ ] Yes
21 | - [ ] No
22 |
23 | If yes, please describe the impact and migration path for existing applications:
24 |
25 | **The PR fulfills these requirements:**
26 |
27 | - [ ] It's submitted to the `dev` branch and _not_ the `master` branch
28 | - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix: #xxx[,#xxx]`, where "xxx" is the issue number)
29 | - [ ] It's been tested on Windows
30 | - [ ] It's been tested on Linux
31 | - [ ] It's been tested on MacOS
32 | - [ ] Any necessary documentation has been added or updated [in the docs](https://github.com/quasarframework/quasar-framework.org/tree/dev/source) (for faster update click on "Suggest an edit on GitHub" at bottom of page) or explained in the PR's description.
33 |
34 | If adding a **new feature**, the PR's description includes:
35 | - [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)
36 |
37 | **Other information:**
38 |
--------------------------------------------------------------------------------
/lib/webpack/ssr/server.js:
--------------------------------------------------------------------------------
1 | const
2 | nodeExternals = require('webpack-node-externals'),
3 | VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
4 |
5 | module.exports = function (chain, cfg) {
6 | chain.entry('app')
7 | .clear()
8 | .add(appPaths.resolve.app('.quasar/server-entry.js'))
9 |
10 | chain.target('node')
11 | chain.devtool('#source-map')
12 |
13 | chain.output
14 | .filename('server-bundle.js')
15 | .libraryTarget('commonjs2')
16 |
17 | chain.plugin('define')
18 | .tap(args => {
19 | const { 'process.env': env, ...rest } = args[0]
20 | return [{
21 | 'process.env': Object.assign(
22 | {},
23 | env,
24 | { CLIENT: false, SERVER: true }
25 | ),
26 | ...rest
27 | }]
28 | })
29 |
30 | chain.externals(nodeExternals({
31 | // do not externalize CSS files in case we need to import it from a dep
32 | whitelist: /(\.css$|\.vue$|\?vue&type=style|quasar-framework[\\/])/
33 | }))
34 |
35 | chain.plugin('vue-ssr-client')
36 | .use(VueSSRServerPlugin, [{
37 | filename: '../vue-ssr-server-bundle.json'
38 | }])
39 |
40 | if (cfg.ctx.prod) {
41 | const SsrProdArtifacts = require('./plugin.ssr-prod-artifacts')
42 | chain.plugin('ssr-artifacts')
43 | .use(SsrProdArtifacts, [ cfg ])
44 |
45 | // copy src-ssr to dist folder in /server
46 | const CopyWebpackPlugin = require('copy-webpack-plugin')
47 | chain.plugin('copy-webpack')
48 | .use(CopyWebpackPlugin, [
49 | [{
50 | from: cfg.ssr.__dir,
51 | to: '../server',
52 | ignore: ['.*']
53 | }]
54 | ])
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/bin/quasar-mode:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const
4 | parseArgs = require('minimist'),
5 | chalk = require('chalk')
6 |
7 | const
8 | log = require('../lib/helpers/logger')('app:mode')
9 | appPaths = require('../lib/app-paths')
10 |
11 | const argv = parseArgs(process.argv.slice(2), {
12 | alias: {
13 | a: 'add',
14 | r: 'remove',
15 | h: 'help'
16 | },
17 | string: ['a', 'r'],
18 | boolean: ['h']
19 | })
20 |
21 | if (argv.help) {
22 | console.log(`
23 | Description
24 | Add/Remove support for PWA / Cordova / Electron modes.
25 | Usage
26 | $ quasar mode -r|-a pwa|ssr|cordova|electron
27 |
28 | # determine what modes are currently installed:
29 | $ quasar mode
30 | Options
31 | --add, -a Add support for mode [pwa|ssr|cordova|electron]
32 | --remove, -r Remove support for mode [pwa|ssr|cordova|electron]
33 | --help, -h Displays this message
34 | `)
35 | process.exit(0)
36 | }
37 |
38 | require('../lib/helpers/ensure-argv')(argv, 'mode')
39 | const
40 | getMode = require('../lib/mode'),
41 | { green, grey } = require('chalk')
42 |
43 | if (argv.add) {
44 | getMode(argv.add).add()
45 | process.exit(0)
46 | }
47 | else if (argv.remove) {
48 | getMode(argv.remove).remove()
49 | process.exit(0)
50 | }
51 |
52 | log(`Detecting installed modes...`)
53 |
54 | const info = []
55 | ;['pwa', 'ssr', 'cordova', 'electron'].forEach(mode => {
56 | const QuasarMode = getMode(mode)
57 | info.push([
58 | `Mode ${mode.toUpperCase()}`,
59 | getMode(mode).isInstalled ? green('yes') : grey('no')
60 | ])
61 | })
62 |
63 | console.log(
64 | '\n' +
65 | info.map(msg => ' ' + msg[0].padEnd(16, '.') + ' ' + msg[1]).join('\n') +
66 | '\n'
67 | )
68 |
--------------------------------------------------------------------------------
/lib/artifacts.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | fse = require('fs-extra')
5 |
6 | const
7 | appPaths = require('./app-paths'),
8 | filePath = appPaths.resolve.app('.quasar/artifacts.json'),
9 | log = require('./helpers/logger')('app:artifacts')
10 |
11 | function exists () {
12 | return fs.existsSync(filePath)
13 | }
14 |
15 | function getArtifacts () {
16 | return exists()
17 | ? require(filePath)
18 | : { folders: [] }
19 | }
20 |
21 | function save (content) {
22 | fse.mkdirp(path.dirname(filePath))
23 | fs.writeFileSync(filePath, JSON.stringify(content), 'utf-8')
24 | }
25 |
26 | module.exports.add = function (entry) {
27 | const content = getArtifacts()
28 |
29 | if (!content.folders.includes(entry)) {
30 | content.folders.push(entry)
31 | save(content)
32 | log(`Added build artifact "${entry}"`)
33 | }
34 | }
35 |
36 | module.exports.clean = function (folder) {
37 | if (folder.endsWith(path.join('src-cordova', 'www'))) {
38 | fse.emptyDirSync(folder)
39 | }
40 | else {
41 | fse.removeSync(folder)
42 | }
43 |
44 | log(`Cleaned build artifact: "${folder}"`)
45 | }
46 |
47 | module.exports.cleanAll = function () {
48 | getArtifacts().folders.forEach(folder => {
49 | if (folder.endsWith(path.join('src-cordova', 'www'))) {
50 | fse.emptyDirSync(folder)
51 | }
52 | else {
53 | fse.removeSync(folder)
54 | }
55 |
56 | log(`Cleaned build artifact: "${folder}"`)
57 | })
58 |
59 | let folder = appPaths.resolve.app('.quasar')
60 | fse.removeSync(folder)
61 | log(`Cleaned build artifact: "${folder}"`)
62 |
63 | fse.emptyDirSync(appPaths.resolve.app('dist'))
64 | log(`Emptied dist folder`)
65 | }
66 |
--------------------------------------------------------------------------------
/lib/webpack/ssr/template.ssr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS FILE IS GENERATED AUTOMATICALLY.
3 | * DO NOT EDIT.
4 | **/
5 |
6 | const
7 | fs = require('fs'),
8 | path = require('path'),
9 | LRU = require('lru-cache'),
10 | { createBundleRenderer } = require('vue-server-renderer')
11 |
12 | const
13 | resolve = file => path.join(__dirname, file),
14 | template = fs.readFileSync(resolve('template.html'), 'utf-8'),
15 | bundle = require('./vue-ssr-server-bundle.json'),
16 | clientManifest = require('./vue-ssr-client-manifest.json')
17 |
18 | const settings = <%= opts %>
19 |
20 | if (process.env.DEBUG) {
21 | settings.debug = true
22 | }
23 |
24 | const rendererOptions = {
25 | template,
26 | clientManifest,
27 | // for component caching
28 | cache: LRU(settings.componentCache),
29 | basedir: __dirname,
30 | // recommended for performance
31 | runInNewContext: false
32 | }
33 |
34 | // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
35 | let renderer = createBundleRenderer(bundle, rendererOptions)
36 |
37 | module.exports.renderToString = function ({ req, res }, cb) {
38 | const ctx = {
39 | url: req.url,
40 | req,
41 | res
42 | }
43 | <% if (flags.meta) { %>
44 | renderer.renderToString(ctx, (err, html) => {
45 | if (err) { cb(err, html) }
46 | else { cb(err, ctx.$getMetaHTML(html)) }
47 | })
48 | <% } else { %>
49 | renderer.renderToString(ctx, cb)
50 | <% } %>
51 | }
52 |
53 | module.exports.resolveWWW = function (file) {
54 | return resolve('www/' + file)
55 | }
56 |
57 | module.exports.mergeRendererOptions = function (opts) {
58 | renderer = createBundleRenderer(
59 | bundle,
60 | Object.assign(rendererOptions, opts)
61 | )
62 | }
63 |
64 | module.exports.settings = settings
65 |
--------------------------------------------------------------------------------
/lib/helpers/net.js:
--------------------------------------------------------------------------------
1 | const
2 | os = require('os'),
3 | net = require('net')
4 |
5 | module.exports.getExternalNetworkInterface = function () {
6 | const
7 | networkInterfaces = os.networkInterfaces(),
8 | devices = []
9 |
10 | for (let deviceName of Object.keys(networkInterfaces)) {
11 | const networkInterface = networkInterfaces[deviceName]
12 |
13 | for (let networkAddress of networkInterface) {
14 | if (!networkAddress.internal && networkAddress.family === 'IPv4') {
15 | devices.push({ deviceName, ...networkAddress })
16 | }
17 | }
18 | }
19 |
20 | return devices
21 | }
22 |
23 | module.exports.findClosestOpenPort = async function (port, host) {
24 | let portProposal = port
25 |
26 | do {
27 | if (await module.exports.isPortAvailable(portProposal, host)) {
28 | return portProposal
29 | }
30 | portProposal++
31 | }
32 | while (portProposal < 65535)
33 |
34 | throw new Error('ERROR_NETWORK_PORT_NOT_AVAIL')
35 | }
36 |
37 | module.exports.isPortAvailable = async function (port, host) {
38 | return new Promise((resolve, reject) => {
39 | const tester = net.createServer()
40 | .once('error', err => {
41 | if (err.code === 'EADDRNOTAVAIL') {
42 | reject(new Error('ERROR_NETWORK_ADDRESS_NOT_AVAIL'))
43 | }
44 | else if (err.code === 'EADDRINUSE') {
45 | resolve(false) // host/port in use
46 | }
47 | else {
48 | reject(err)
49 | }
50 | })
51 | .once('listening', () => {
52 | tester.once('close', () => {
53 | resolve(true) // found available host/port
54 | })
55 | .close()
56 | })
57 | .on('error', err => {
58 | reject(err)
59 | })
60 | .listen(port, host)
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/lib/cordova/index.js:
--------------------------------------------------------------------------------
1 | const
2 | log = require('../helpers/logger')('app:cordova'),
3 | CordovaConfig = require('./cordova-config'),
4 | spawn = require('../helpers/spawn'),
5 | onShutdown = require('../helpers/on-shutdown'),
6 | appPaths = require('../app-paths')
7 |
8 | class CordovaRunner {
9 | constructor () {
10 | this.pid = 0
11 | this.config = new CordovaConfig()
12 |
13 | onShutdown(() => {
14 | this.stop()
15 | })
16 | }
17 |
18 | run (quasarConfig) {
19 | const url = quasarConfig.getBuildConfig().build.APP_URL
20 |
21 | if (this.url === url) {
22 | return
23 | }
24 |
25 | if (this.pid) {
26 | this.stop()
27 | }
28 |
29 | this.url = url
30 |
31 | const
32 | cfg = quasarConfig.getBuildConfig(),
33 | args = ['run', cfg.ctx.targetName]
34 |
35 | if (cfg.ctx.emulator) {
36 | args.push(`--target=${cfg.ctx.emulator}`)
37 | }
38 |
39 | if (cfg.ctx.targetName === 'ios') {
40 | args.push(`--buildFlag=-UseModernBuildSystem=0`)
41 | }
42 |
43 | return this.__runCordovaCommand(
44 | cfg,
45 | args
46 | )
47 | }
48 |
49 | build (quasarConfig) {
50 | const cfg = quasarConfig.getBuildConfig()
51 |
52 | return this.__runCordovaCommand(
53 | cfg,
54 | ['build', cfg.ctx.debug ? '--debug' : '--release', cfg.ctx.targetName]
55 | )
56 | }
57 |
58 | stop () {
59 | if (!this.pid) { return }
60 |
61 | log('Shutting down Cordova process...')
62 | process.kill(this.pid)
63 | this.__cleanup()
64 | }
65 |
66 | __runCordovaCommand (cfg, args) {
67 | this.config.prepare(cfg)
68 |
69 | return new Promise((resolve, reject) => {
70 | this.pid = spawn(
71 | 'cordova',
72 | args,
73 | appPaths.cordovaDir,
74 | code => {
75 | this.__cleanup()
76 | resolve(code)
77 | }
78 | )
79 | })
80 | }
81 |
82 | __cleanup () {
83 | this.pid = 0
84 | this.config.reset()
85 | }
86 | }
87 |
88 | module.exports = new CordovaRunner()
89 |
--------------------------------------------------------------------------------
/lib/webpack/plugin.html-addons.js:
--------------------------------------------------------------------------------
1 | const appPaths = require('../app-paths')
2 |
3 | function makeTag (tagName, attributes, closeTag = false) {
4 | return {
5 | tagName,
6 | attributes,
7 | closeTag
8 | }
9 | }
10 |
11 | function makeScriptTag (innerHTML) {
12 | return {
13 | tagName: 'script',
14 | closeTag: true,
15 | innerHTML
16 | }
17 | }
18 |
19 | function fillHtmlTags (data, cfg) {
20 | if (cfg.build.appBase) {
21 | data.head.push(
22 | makeTag('base', { href: cfg.build.appBase })
23 | )
24 | }
25 | }
26 |
27 | module.exports.fillHtmlTags = fillHtmlTags
28 |
29 | module.exports.plugin = class HtmlAddonsPlugin {
30 | constructor (cfg = {}) {
31 | this.cfg = cfg
32 | }
33 |
34 | apply (compiler) {
35 | compiler.hooks.compilation.tap('webpack-plugin-html-addons', compilation => {
36 | compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('webpack-plugin-html-addons', (data, callback) => {
37 | fillHtmlTags(data, this.cfg)
38 |
39 | if (this.cfg.ctx.mode.electron && this.cfg.ctx.dev) {
40 | data.head.push(
41 | makeScriptTag(`
42 | require('module').globalPaths.push('${appPaths.resolve.app('node_modules').replace(/\\/g, '\\\\')}')
43 | `)
44 | )
45 | }
46 |
47 | if (this.cfg.ctx.mode.cordova) {
48 | data.body.unshift(
49 | makeTag('script', { src: 'cordova.js' }, true)
50 | )
51 | }
52 | else if (this.cfg.ctx.mode.electron && this.cfg.ctx.prod) {
53 | // set statics path in production;
54 | // the reason we add this is here is because the folder path
55 | // needs to be evaluated at runtime
56 | const bodyScript = `
57 | window.__statics = require('path').join(__dirname, 'statics').replace(/\\\\/g, '\\\\');
58 | `
59 | data.body.push(
60 | makeScriptTag(bodyScript)
61 | )
62 | }
63 |
64 | // finally, inform Webpack that we're ready
65 | callback(null, data)
66 | })
67 | })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/templates/entry/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS FILE IS GENERATED AUTOMATICALLY.
3 | * DO NOT EDIT.
4 | *
5 | * You are probably looking on adding initialization code.
6 | * Use "quasar new plugin " and add it there.
7 | * One plugin per concern. Then reference the file(s) in quasar.conf.js > plugins:
8 | * plugins: ['file', ...] // do not add ".js" extension to it.
9 | **/
10 | import './import-quasar.js'
11 |
12 | <% if (ctx.mode.ssr) { %>
13 | import <%= framework.all ? 'Quasar' : '{ Quasar }' %> from 'quasar'
14 | <% } %>
15 |
16 | import App from 'app/<%= sourceFiles.rootComponent %>'
17 |
18 | <% if (store) { %>
19 | import createStore from 'app/<%= sourceFiles.store %>'
20 | <% } %>
21 | import createRouter from 'app/<%= sourceFiles.router %>'
22 |
23 | export default function (<%= ctx.mode.ssr ? 'ssrContext' : '' %>) {
24 | // create store and router instances
25 | <% if (store) { %>
26 | const store = typeof createStore === 'function'
27 | ? createStore(<%= ctx.mode.ssr ? '{ ssrContext }' : '' %>)
28 | : createStore
29 | <% } %>
30 | const router = typeof createRouter === 'function'
31 | ? createRouter({<%= ctx.mode.ssr ? 'ssrContext' + (store ? ', ' : '') : '' %><%= store ? 'store' : '' %>})
32 | : createRouter
33 | <% if (store) { %>
34 | // make router instance available in store
35 | store.$router = router
36 | <% } %>
37 |
38 | // Create the app instantiation Object.
39 | // Here we inject the router, store to all child components,
40 | // making them available everywhere as `this.$router` and `this.$store`.
41 | const app = {
42 | <% if (!ctx.mode.ssr) { %>el: '#q-app',<% } %>
43 | router,
44 | <%= store ? 'store,' : '' %>
45 | render: h => h(App)
46 | }
47 |
48 | <% if (ctx.mode.ssr) { %>
49 | Quasar.ssrUpdate({ app, ssr: ssrContext })
50 | <% } %>
51 |
52 | // expose the app, the router and the store.
53 | // note we are not mounting the app here, since bootstrapping will be
54 | // different depending on whether we are in a browser or on the server.
55 | return {
56 | app,
57 | <%= store ? 'store,' : '' %>
58 | router
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/webpack/pwa/plugin.html-pwa.js:
--------------------------------------------------------------------------------
1 | function makeTag (tagName, attributes) {
2 | return {
3 | tagName,
4 | attributes,
5 | closeTag: false
6 | }
7 | }
8 |
9 | function fillPwaTags (data, { pwa: { manifest }}) {
10 | data.head.push(
11 | // Add to home screen for Android and modern mobile browsers
12 | makeTag('link', {
13 | rel: 'manifest',
14 | href: 'manifest.json'
15 | }),
16 | makeTag('meta', {
17 | name: 'theme-color',
18 | content: manifest.theme_color
19 | }),
20 |
21 | // Add to home screen for Safari on iOS
22 | makeTag('meta', {
23 | name: 'apple-mobile-web-app-capable',
24 | content: 'yes'
25 | }),
26 | makeTag('meta', {
27 | name: 'apple-mobile-web-app-status-bar-style',
28 | content: manifest.background_color
29 | }),
30 | makeTag('meta', {
31 | name: 'apple-mobile-web-app-title',
32 | content: manifest.name
33 | }),
34 | makeTag('link', {
35 | rel: 'apple-touch-icon',
36 | href: 'statics/icons/apple-icon-152x152.png'
37 | }),
38 | /* makeTag('link', {
39 | rel: 'mask-icon',
40 | href: 'statics/icons/safari-pinned-tab.svg',
41 | color: manifest.theme_color
42 | }), */
43 |
44 | // Add to home screen for Windows
45 | makeTag('meta', {
46 | name: 'msapplication-TileImage',
47 | content: 'statics/icons/ms-icon-144x144.png'
48 | }),
49 | makeTag('meta', {
50 | name: 'msapplication-TileColor',
51 | content: manifest.background_color
52 | })
53 | )
54 | }
55 |
56 | module.exports.fillPwaTags = fillPwaTags
57 |
58 | module.exports.plugin = class HtmlPwaPlugin {
59 | constructor (cfg = {}) {
60 | this.cfg = cfg
61 | }
62 |
63 | apply (compiler) {
64 | compiler.hooks.compilation.tap('webpack-plugin-html-pwa', compilation => {
65 | compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('webpack-plugin-html-pwa', (data, callback) => {
66 | fillPwaTags(data, this.cfg)
67 |
68 | // finally, inform Webpack that we're ready
69 | callback(null, data)
70 | })
71 | })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/mode/mode-electron.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | fse = require('fs-extra'),
4 | appPaths = require('../app-paths'),
5 | logger = require('../helpers/logger'),
6 | log = logger('app:mode-electron'),
7 | warn = logger('app:mode-electron', 'red'),
8 | spawn = require('../helpers/spawn'),
9 | nodePackager = require('../helpers/node-packager')
10 |
11 | const
12 | electronDeps = {
13 | 'electron': '4.0.5',
14 | 'electron-debug': '2.1.0',
15 | 'electron-devtools-installer': '2.2.4',
16 | 'devtron': '1.4.0'
17 | }
18 |
19 | class Mode {
20 | get isInstalled () {
21 | return fs.existsSync(appPaths.electronDir)
22 | }
23 |
24 | add (params) {
25 | if (this.isInstalled) {
26 | warn(`Electron support detected already. Aborting.`)
27 | return
28 | }
29 |
30 | const cmdParam = nodePackager === 'npm'
31 | ? ['install', '--save-dev']
32 | : ['add', '--dev']
33 |
34 | log(`Installing Electron dependencies...`)
35 | spawn.sync(
36 | nodePackager,
37 | cmdParam.concat(Object.keys(electronDeps).map(dep => {
38 | return `${dep}@${electronDeps[dep]}`
39 | })),
40 | appPaths.appDir,
41 | () => warn('Failed to install Electron dependencies')
42 | )
43 |
44 | log(`Creating Electron source folder...`)
45 | fse.copySync(appPaths.resolve.cli('templates/electron'), appPaths.electronDir)
46 | log(`Electron support was added`)
47 | }
48 |
49 | remove () {
50 | if (!this.isInstalled) {
51 | warn(`No Electron support detected. Aborting.`)
52 | return
53 | }
54 |
55 | log(`Removing Electron source folder`)
56 | fse.removeSync(appPaths.electronDir)
57 |
58 | const cmdParam = nodePackager === 'npm'
59 | ? ['uninstall', '--save-dev']
60 | : ['remove', '--dev']
61 |
62 | log(`Uninstalling Electron dependencies...`)
63 | spawn.sync(
64 | nodePackager,
65 | cmdParam.concat(Object.keys(electronDeps)),
66 | appPaths.appDir,
67 | () => warn('Failed to uninstall Electron dependencies')
68 | )
69 |
70 | log(`Electron support was removed`)
71 | }
72 | }
73 |
74 | module.exports = Mode
75 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 | ### Software version
30 |
31 | OS:
32 | Node:
33 | NPM:
34 | Any other software related to your bug:
35 |
36 | ### What did you get as the error?
37 |
38 | ### What were you expecting?
39 |
40 | ### What steps did you take, to get the error?
41 |
--------------------------------------------------------------------------------
/templates/ssr/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only
3 | * the ES6 features that are supported by your Node version. https://node.green/
4 | *
5 | * All content of this folder will be copied as is to the output folder. So only import:
6 | * 1. node_modules (and yarn/npm install dependencies -- NOT to devDependecies though)
7 | * 2. create files in this folder and import only those with the relative path
8 | *
9 | * Note: This file is used only for PRODUCTION. It is not picked up while in dev mode.
10 | * If you are looking to add common DEV & PROD logic to the express app, then use
11 | * "src-ssr/extension.js"
12 | */
13 |
14 | const
15 | express = require('express'),
16 | compression = require('compression')
17 |
18 | const
19 | ssr = require('../ssr'),
20 | extension = require('./extension'),
21 | app = express(),
22 | port = process.env.PORT || 3000
23 |
24 | const serve = (path, cache) => express.static(ssr.resolveWWW(path), {
25 | maxAge: cache ? 1000 * 60 * 60 * 24 * 30 : 0
26 | })
27 |
28 | // gzip
29 | app.use(compression({ threshold: 0 }))
30 |
31 | // serve this with no cache, if built with PWA:
32 | if (ssr.settings.pwa) {
33 | app.use('/service-worker.js', serve('service-worker.js'))
34 | }
35 |
36 | // serve "www" folder
37 | app.use('/', serve('.', true))
38 |
39 | // we extend the custom common dev & prod parts here
40 | extension.extendApp({ app })
41 |
42 | // this should be last get(), rendering with SSR
43 | app.get('*', (req, res) => {
44 | res.setHeader('Content-Type', 'text/html')
45 | ssr.renderToString({ req, res }, (err, html) => {
46 | if (err) {
47 | if (err.url) {
48 | res.redirect(err.url)
49 | }
50 | else if (err.code === 404) {
51 | res.status(404).send('404 | Page Not Found')
52 | }
53 | else {
54 | // Render Error Page or Redirect
55 | res.status(500).send('500 | Internal Server Error')
56 | if (ssr.settings.debug) {
57 | console.error(`500 on ${req.url}`)
58 | console.error(err)
59 | console.error(err.stack)
60 | }
61 | }
62 | }
63 | else {
64 | res.send(html)
65 | }
66 | })
67 | })
68 |
69 | app.listen(port, () => {
70 | console.log(`Server listening at port ${port}`)
71 | })
72 |
--------------------------------------------------------------------------------
/lib/ssr/html-template.js:
--------------------------------------------------------------------------------
1 | const
2 | compileTemplate = require('lodash.template'),
3 | HtmlWebpackPlugin = require('html-webpack-plugin'),
4 | { fillHtmlTags } = require('../webpack/plugin.html-addons'),
5 | { fillPwaTags } = require('../webpack/pwa/plugin.html-pwa')
6 |
7 | function injectSsrInterpolation (html) {
8 | return html
9 | .replace(
10 | /(]*)(>)/i,
11 | (found, start, end) => {
12 | let matches
13 |
14 | matches = found.match(/\sdir\s*=\s*['"]([^'"]*)['"]/i)
15 | if (matches) {
16 | start = start.replace(matches[0], '')
17 | }
18 |
19 | matches = found.match(/\slang\s*=\s*['"]([^'"]*)['"]/i)
20 | if (matches) {
21 | start = start.replace(matches[0], '')
22 | }
23 |
24 | return `${start} {{ Q_HTML_ATTRS }}${end}`
25 | }
26 | )
27 | .replace(
28 | /(]*)(>)/i,
29 | (found, start, end) => `${start}${end}{{ Q_HEAD_TAGS }}`
30 | )
31 | .replace(
32 | /(]*)(>)/i,
33 | (found, start, end) => {
34 | let classes = '{{ Q_BODY_CLASSES }}'
35 |
36 | const matches = found.match(/\sclass\s*=\s*['"]([^'"]*)['"]/i)
37 |
38 | if (matches) {
39 | if (matches[1].length > 0) {
40 | classes += ` ${matches[1]}`
41 | }
42 | start = start.replace(matches[0], '')
43 | }
44 |
45 | return `${start} class="${classes.trim()}" {{ Q_BODY_ATTRS }}${end}{{ Q_BODY_TAGS }}`
46 | }
47 | )
48 | }
49 |
50 | module.exports.getIndexHtml = function (template, cfg) {
51 | const compiled = compileTemplate(
52 | template.replace('', '')
53 | )
54 | let html = compiled({
55 | htmlWebpackPlugin: {
56 | options: cfg.__html.variables
57 | }
58 | })
59 |
60 | const data = { body: [], head: [] }
61 |
62 | fillHtmlTags(data, cfg)
63 |
64 | if (cfg.ctx.mode.pwa) {
65 | fillPwaTags(data, cfg)
66 | }
67 |
68 | html = HtmlWebpackPlugin.prototype.injectAssetsIntoHtml(html, {}, data)
69 | html = injectSsrInterpolation(html)
70 |
71 | if (cfg.build.minify) {
72 | const { minify } = require('html-minifier')
73 | html = minify(html, Object.assign({}, cfg.__html.minifyOptions, {
74 | ignoreCustomComments: [ /vue-ssr-outlet/ ],
75 | ignoreCustomFragments: [ /{{ [\s\S]*? }}/ ]
76 | }))
77 | }
78 |
79 | return html
80 | }
81 |
--------------------------------------------------------------------------------
/lib/generator.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | fse = require('fs-extra'),
4 | path = require('path'),
5 | compileTemplate = require('lodash.template')
6 |
7 | const
8 | log = require('./helpers/logger')('app:generator')
9 | appPaths = require('./app-paths'),
10 | quasarFolder = appPaths.resolve.app('.quasar')
11 |
12 | class Generator {
13 | constructor (quasarConfig) {
14 | const { ctx, loadingBar, preFetch } = quasarConfig.getBuildConfig()
15 |
16 | this.alreadyGenerated = false
17 | this.quasarConfig = quasarConfig
18 |
19 | const paths = [
20 | 'app.js',
21 | 'client-entry.js',
22 | 'import-quasar.js'
23 | ]
24 |
25 | if (preFetch) {
26 | paths.push('client-prefetch.js')
27 | }
28 | if (ctx.mode.ssr) {
29 | paths.push('server-entry.js')
30 | }
31 |
32 | this.files = paths.map(file => {
33 | const
34 | content = fs.readFileSync(
35 | appPaths.resolve.cli(`templates/entry/${file}`),
36 | 'utf-8'
37 | ),
38 | filename = path.basename(file)
39 |
40 | return {
41 | filename,
42 | dest: path.join(quasarFolder, filename),
43 | template: compileTemplate(content)
44 | }
45 | })
46 | }
47 |
48 | prepare () {
49 | const
50 | now = Date.now() / 1000,
51 | then = now - 100,
52 | appVariablesFile = appPaths.resolve.cli('templates/app/variables.styl'),
53 | appStylFile = appPaths.resolve.cli('templates/app/app.styl'),
54 | emptyStylFile = path.join(quasarFolder, 'empty.styl')
55 |
56 | function copy (file) {
57 | const dest = path.join(quasarFolder, path.basename(file))
58 | fse.copySync(file, dest)
59 | fs.utimes(dest, then, then, function (err) { if (err) throw err })
60 | }
61 |
62 | copy(appStylFile)
63 | copy(appVariablesFile)
64 |
65 | fs.writeFileSync(emptyStylFile, '', 'utf-8'),
66 | fs.utimes(emptyStylFile, then, then, function (err) { if (err) throw err })
67 | }
68 |
69 | build () {
70 | log(`Generating Webpack entry point`)
71 | const data = this.quasarConfig.getBuildConfig()
72 |
73 | this.files.forEach(file => {
74 | fs.writeFileSync(file.dest, file.template(data), 'utf-8')
75 | })
76 |
77 | if (!this.alreadyGenerated) {
78 | const then = Date.now() / 1000 - 120
79 |
80 | this.files.forEach(file => {
81 | fs.utimes(file.dest, then, then, function (err) { if (err) throw err })
82 | })
83 |
84 | this.alreadyGenerated = true
85 | }
86 | }
87 | }
88 |
89 | module.exports = Generator
90 |
--------------------------------------------------------------------------------
/bin/quasar-init:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const parseArgs = require('minimist')
4 |
5 | const argv = parseArgs(process.argv.slice(2), {
6 | alias: {
7 | v: 'version',
8 | t: 'type',
9 | h: 'help'
10 | },
11 | boolean: ['h'],
12 | string: ['v', 't']
13 | })
14 |
15 | if (argv.help) {
16 | console.log(`
17 | Description
18 | Create a Quasar App folder from the starter kit.
19 | You need "vue-cli" package installed globally.
20 | Usage
21 | # Install latest starter kit:
22 | $ quasar init
23 |
24 | # Install starter kit for specific Quasar version.
25 | # Only specify the major and minor version (no patch version).
26 | # Good example: 0.15, 1.0, 1.2
27 | # Bad example: 0.15.2, 1.0.1, 1.2.2
28 | $ quasar init -v 0.15
29 |
30 | # Install UMD starter kit
31 | $ quasar init -t umd
32 |
33 | Options
34 | --version, -v Install specific Quasar version
35 | --type, -t Install specific starter kit
36 | --help, -h Displays this message
37 | `)
38 | process.exit(0)
39 | }
40 |
41 | const
42 | logger = require('../lib/helpers/logger'),
43 | log = logger('app:init'),
44 | warn = logger('app:init', 'red'),
45 | spawn = require('cross-spawn'),
46 | resolve = require('path').resolve
47 |
48 | if (argv.type && !['umd'].includes(argv.type)) {
49 | warn(`Specified type ("${argv.type}") is not recognized.`)
50 | warn()
51 | process.exit(0)
52 | }
53 |
54 | if (!argv._[0]) {
55 | warn(`Missing folder name as parameter.`)
56 | warn()
57 | process.exit(0)
58 | }
59 |
60 | const
61 | cliDir = resolve(__dirname, '..')
62 |
63 | let template = `quasarframework/quasar-starter-kit${argv.type ? `-${argv.type}` : ''}`
64 | if (argv.version) {
65 | template += `#v${argv.version}`
66 | }
67 |
68 | try {
69 | console.log(` Running command: vue init '${template}' ${argv._[0]}`)
70 | const child = spawn.sync('vue', [
71 | 'init',
72 | template,
73 | argv._[0]
74 | ], { stdio: ['inherit', 'inherit', 'inherit'] })
75 |
76 | if (child.status !== 0) {
77 | warn(`⚠️ Something went wrong... Try running the "vue init" command above manually.`)
78 | warn(`Reasons for failure: Package @vue/cli and @vue/cli-init are not installed globally, specified template is unavailable or it failed to download.`)
79 | warn()
80 | process.exit(1)
81 | }
82 | }
83 | catch (err) {
84 | console.log(err)
85 | warn(`⚠️ Package vue-cli not installed globally.`)
86 | warn('Run "yarn global add @vue/cli @vue/cli-init" or "npm i -g @vue/cli @vue/cli-init" to install Vue CLI and then try again.')
87 | warn()
88 | process.exit(1)
89 | }
90 |
--------------------------------------------------------------------------------
/lib/webpack/pwa/index.js:
--------------------------------------------------------------------------------
1 | const
2 | appPaths = require('../../app-paths'),
3 | PwaManifestPlugin = require('./plugin.pwa-manifest')
4 |
5 | module.exports = function (chain, cfg) {
6 | // write manifest.json file
7 | chain.plugin('pwa-manifest')
8 | .use(PwaManifestPlugin, [ cfg ])
9 |
10 | let defaultOptions
11 | const
12 | WorkboxPlugin = require('workbox-webpack-plugin'),
13 | pluginMode = cfg.pwa.workboxPluginMode,
14 | log = require('../../helpers/logger')('app:workbox')
15 |
16 | if (pluginMode === 'GenerateSW') {
17 | const pkg = require(appPaths.resolve.app('package.json'))
18 |
19 | defaultOptions = {
20 | cacheId: pkg.name || 'quasar-pwa-app'
21 | }
22 |
23 | log('[GenerateSW] Will generate a service-worker file. Ignoring your custom written one.')
24 | }
25 | else {
26 | defaultOptions = {
27 | swSrc: appPaths.resolve.app(cfg.sourceFiles.serviceWorker)
28 | }
29 |
30 | log('[InjectManifest] Using your custom service-worker written file')
31 | }
32 |
33 | let opts = Object.assign(
34 | defaultOptions,
35 | cfg.pwa.workboxOptions
36 | )
37 |
38 | if (cfg.ctx.mode.ssr) {
39 | if (!opts.directoryIndex) {
40 | opts.directoryIndex = '/'
41 | }
42 |
43 | // if Object form:
44 | if (cfg.ssr.pwa && cfg.ssr.pwa !== true) {
45 | const merge = require('webpack-merge')
46 | opts = merge(opts, cfg.ssr.pwa)
47 | }
48 |
49 | if (pluginMode === 'GenerateSW') {
50 | opts.runtimeCaching = opts.runtimeCaching || []
51 | if (!opts.runtimeCaching.find(entry => entry.urlPattern === '/')) {
52 | opts.runtimeCaching.unshift({
53 | urlPattern: '/',
54 | handler: 'networkFirst'
55 | })
56 | }
57 | }
58 | }
59 |
60 | if (cfg.ctx.dev) {
61 | log('⚠️ Forcing PWA into the network-first approach to not break Hot Module Replacement while developing.')
62 | // forcing network-first strategy
63 | opts.chunks = [ 'quasar-bogus-chunk' ]
64 | if (opts.excludeChunks) {
65 | delete opts.excludeChunks
66 | }
67 |
68 | if (pluginMode === 'GenerateSW') {
69 | opts.runtimeCaching = opts.runtimeCaching || []
70 | opts.runtimeCaching.push({
71 | urlPattern: /^http/,
72 | handler: 'networkFirst'
73 | })
74 | }
75 | }
76 |
77 | opts.swDest = 'service-worker.js'
78 |
79 | chain.plugin('workbox')
80 | .use(WorkboxPlugin[pluginMode], [ opts ])
81 |
82 | if (!cfg.ctx.mode.ssr) {
83 | const HtmlPwaPlugin = require('./plugin.html-pwa').plugin
84 | chain.plugin('html-pwa')
85 | .use(HtmlPwaPlugin, [ cfg ])
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/electron/bundler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const
3 | appPath = require('../app-paths'),
4 | packagerVersion = '13.1.0',
5 | log = require('../helpers/logger')('app:electron-bundle')
6 |
7 | function isValidName (bundlerName) {
8 | return ['packager', 'builder'].includes(bundlerName)
9 | }
10 |
11 | function installBundler (bundlerName) {
12 | const
13 | spawn = require('../helpers/spawn'),
14 | nodePackager = require('../helpers/node-packager'),
15 | version = bundlerName === 'packager' ? `^${packagerVersion}` : 'latest',
16 | cmdParam = nodePackager === 'npm'
17 | ? ['install', '--save-dev']
18 | : ['add', '--dev']
19 |
20 | log(`Installing required Electron bundler (electron-${bundlerName})...`)
21 | spawn.sync(
22 | nodePackager,
23 | cmdParam.concat([`electron-${bundlerName}@${version}`]),
24 | appPaths.appDir,
25 | () => warn(`Failed to install electron-${bundlerName}`)
26 | )
27 | }
28 |
29 | function isInstalled (bundlerName) {
30 | return fs.existsSync(appPath.resolve.app(`node_modules/electron-${bundlerName}`))
31 | }
32 |
33 | module.exports.ensureInstall = function (bundlerName) {
34 | if (!isValidName(bundlerName)) {
35 | warn(`⚠️ Unknown bundler "${ bundlerName }" for Electron`)
36 | warn()
37 | process.exit(1)
38 | }
39 |
40 | if (bundlerName === 'packager') {
41 | if (isInstalled('packager')) {
42 | const
43 | semver = require('semver'),
44 | pkg = require(appPath.resolve.app(`node_modules/electron-${bundlerName}/package.json`))
45 |
46 | if (semver.satisfies(pkg.version, `>= ${packagerVersion}`)) {
47 | return
48 | }
49 | }
50 | }
51 | else if (isInstalled('builder')) {
52 | return
53 | }
54 |
55 | installBundler(bundlerName)
56 | }
57 |
58 | module.exports.getDefaultName = function () {
59 | if (isInstalled('packager')) {
60 | return 'packager'
61 | }
62 |
63 | if (isInstalled('builder')) {
64 | return 'builder'
65 | }
66 |
67 | return 'packager'
68 | }
69 |
70 | module.exports.getBundler = function (bundlerName) {
71 | return require(appPath.resolve.app(`node_modules/electron-${bundlerName}`))
72 | }
73 |
74 | module.exports.ensureBuilderCompatibility = function () {
75 | if (fs.existsSync(appPaths.resolve.electron('icons/linux-256x256.png'))) {
76 | console.log()
77 | console.log(`\n⚠️ electron-builder requires a change to your src-electron/icons folder:
78 | * replace linux-256x256.png with a 512x512 px png file named "linux-512x512.png"
79 | * make sure to delete the old linux-256x256.png file
80 | `)
81 | console.log()
82 | process.exit(1)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/templates/app/layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 | Header
16 | Subtile
17 |
18 |
19 |
20 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
47 |
48 | Footer
49 | Subtile
50 |
51 |
52 |
53 |
60 |
67 |
68 |
69 |
70 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
99 |
100 |
102 |
--------------------------------------------------------------------------------
/lib/helpers/ensure-argv.js:
--------------------------------------------------------------------------------
1 | const warn = require('./logger')('app:ensure-argv', 'red')
2 |
3 | module.exports = function (argv, cmd) {
4 | if (cmd === 'mode') {
5 | if (![undefined, 'pwa', 'cordova', 'electron', 'ssr'].includes(argv.add)) {
6 | warn(`⚠️ Unknown mode "${ argv.add }" to add`)
7 | warn()
8 | process.exit(1)
9 | }
10 | if (![undefined, 'pwa', 'cordova', 'electron', 'ssr'].includes(argv.remove)) {
11 | warn(`⚠️ Unknown mode "${ argv.remove }" to remove`)
12 | warn()
13 | process.exit(1)
14 | }
15 |
16 | return
17 | }
18 |
19 | if (!['spa', 'pwa', 'cordova', 'electron', 'ssr'].includes(argv.mode)) {
20 | warn(`⚠️ Unknown mode "${ argv.mode }"`)
21 | warn()
22 | process.exit(1)
23 | }
24 |
25 | if (!['mat', 'ios'].includes(argv.theme)) {
26 | warn(`⚠️ Unknown theme "${ argv.theme }"`)
27 | warn()
28 | process.exit(1)
29 | }
30 |
31 | if (argv.mode === 'cordova') {
32 | const targets = ['android', 'ios', 'blackberry10', 'browser', 'osx', 'ubuntu', 'webos', 'windows']
33 | if (!argv.target) {
34 | warn(`⚠️ Please also specify a target (-T <${targets.join('|')}>)`)
35 | warn()
36 | process.exit(1)
37 | }
38 | if (!targets.includes(argv.target)) {
39 | warn(`⚠️ Unknown target "${ argv.target }" for Cordova`)
40 | warn()
41 | process.exit(1)
42 | }
43 | }
44 |
45 | if (cmd === 'build' && argv.mode === 'electron') {
46 | if (![undefined, 'packager', 'builder'].includes(argv.bundler)) {
47 | warn(`⚠️ Unknown bundler "${ argv.bundler }" for Electron`)
48 | warn()
49 | process.exit(1)
50 | }
51 |
52 | if ([undefined, 'packager'].includes(argv.bundler)) {
53 | if (![undefined, 'all', 'darwin', 'win32', 'linux', 'mas'].includes(argv.target)) {
54 | warn(`⚠️ Unknown target "${ argv.target }" for electron-packager`)
55 | warn()
56 | process.exit(1)
57 | }
58 | if (![undefined, 'ia32', 'x64', 'armv7l', 'arm64', 'mips64el', 'all'].includes(argv.arch)) {
59 | warn(`⚠️ Unknown architecture "${ argv.arch }" for electron-packager`)
60 | warn()
61 | process.exit(1)
62 | }
63 | }
64 | else { // electron-builder bundler
65 | if (![undefined, 'all', 'darwin', 'mac', 'win32', 'win', 'linux'].includes(argv.target)) {
66 | warn(`⚠️ Unknown target "${ argv.target }" for electron-builder`)
67 | warn()
68 | process.exit(1)
69 | }
70 | if (![undefined, 'ia32', 'x64', 'armv7l', 'arm64', 'all'].includes(argv.arch)) {
71 | warn(`⚠️ Unknown architecture "${ argv.arch }" for electron-builder`)
72 | warn()
73 | process.exit(1)
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/webpack/inject.style-rules.js:
--------------------------------------------------------------------------------
1 | const ExtractLoader = require('mini-css-extract-plugin').loader
2 |
3 | const
4 | appPaths = require('../app-paths'),
5 | postCssConfig = require(appPaths.resolve.app('.postcssrc.js'))
6 |
7 | function injectRule ({ chain, pref }, lang, test, loader, options) {
8 | const baseRule = chain.module.rule(lang).test(test)
9 |
10 | // rules for