├── .editorconfig
├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── assets
└── logo.art
├── bin
├── quasar
├── quasar-build
├── quasar-clean
├── quasar-dev
├── quasar-help
├── quasar-info
├── quasar-init
├── quasar-mode
├── quasar-new
└── quasar-serve
├── lib
├── app-paths.js
├── artifacts.js
├── cordova
│ ├── cordova-config.js
│ └── index.js
├── dev-server.js
├── electron
│ ├── bundler.js
│ └── index.js
├── generator.js
├── helpers
│ ├── animations.js
│ ├── banner.js
│ ├── cli-error-handling.js
│ ├── ensure-argv.js
│ ├── ensure-deps.js
│ ├── get-external-ip.js
│ ├── is-minimal-terminal.js
│ ├── logger.js
│ ├── net.js
│ ├── node-packager.js
│ ├── on-shutdown.js
│ └── spawn.js
├── legacy-validations.js
├── mode
│ ├── index.js
│ ├── install-missing.js
│ ├── mode-cordova.js
│ ├── mode-electron.js
│ ├── mode-pwa.js
│ └── mode-ssr.js
├── node-version-check.js
├── quasar-config.js
├── ssr
│ └── html-template.js
└── webpack
│ ├── cordova
│ └── index.js
│ ├── create-chain.js
│ ├── electron
│ ├── main.js
│ ├── plugin.electron-package-json.js
│ └── renderer.js
│ ├── index.js
│ ├── inject.client-specifics.js
│ ├── inject.hot-update.js
│ ├── inject.html.js
│ ├── inject.preload.js
│ ├── inject.style-rules.js
│ ├── plugin.html-addons.js
│ ├── plugin.progress.js
│ ├── pwa
│ ├── index.js
│ ├── plugin.html-pwa.js
│ └── plugin.pwa-manifest.js
│ ├── spa
│ └── index.js
│ └── ssr
│ ├── client.js
│ ├── plugin.ssr-prod-artifacts.js
│ ├── server.js
│ └── template.ssr.js
├── lists
└── app-templates.json
├── package.json
├── templates
├── app
│ ├── app.styl
│ ├── babelrc
│ ├── component.vue
│ ├── layout.vue
│ ├── page.vue
│ ├── plugin.js
│ ├── store
│ │ ├── actions.js
│ │ ├── getters.js
│ │ ├── index.js
│ │ ├── mutations.js
│ │ └── state.js
│ └── variables.styl
├── electron
│ ├── icons
│ │ ├── icon.icns
│ │ ├── icon.ico
│ │ └── linux-512x512.png
│ └── main-process
│ │ ├── electron-main.dev.js
│ │ └── electron-main.js
├── entry
│ ├── app.js
│ ├── client-entry.js
│ ├── client-prefetch.js
│ ├── import-quasar.js
│ └── server-entry.js
├── pwa
│ ├── custom-service-worker.js
│ └── register-service-worker.js
└── ssr
│ ├── extension.js
│ └── index.js
└── yarn.lock
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Quasar Contributing Guide
2 |
3 | Hi! I’m really excited that you are interested in contributing to Quasar. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
4 |
5 | - [Code of Conduct](https://github.com/quasarframework/quasar/blob/dev/.github/CODE_OF_CONDUCT.md)
6 | - [Issue Reporting Guidelines](#issue-reporting-guidelines)
7 | - [Pull Request Guidelines](#pull-request-guidelines)
8 | - [Development Setup](#development-setup)
9 | - [Project Structure](#project-structure)
10 |
11 | ## Issue Reporting Guidelines
12 |
13 | - The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately.
14 |
15 | - For simple beginner questions, you can get quick answers from the [Quasar Discord chat room](http://chat.quasar-framework.org).
16 |
17 | - For more complicated questions, you can use [the official forum](https://forum.quasar-framework.org/). Make sure to provide enough information when asking your questions - this makes it easier for others to help you!
18 |
19 | - Try to search for your issue, it may have already been answered or even fixed in the development branch (`dev`).
20 |
21 | - Check if the issue is reproducible with the latest stable version of Quasar. If you are using a pre-release, please indicate the specific version you are using.
22 |
23 | - It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable.
24 |
25 | - Use only the minimum amount of code necessary to reproduce the unexpected behavior. A good bug report should isolate specific methods that exhibit unexpected behavior and precisely define how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? The more precisely you isolate the issue, the faster we can investigate.
26 |
27 | - Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed.
28 |
29 | - If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
30 |
31 | - Most importantly, we beg your patience: the team must balance your request against many other responsibilities — fixing other bugs, answering other questions, new features, new documentation, etc. The issue list is not paid support and we cannot make guarantees about how fast your issue can be resolved.
32 |
33 | ## Pull Request Guidelines
34 |
35 | - The `master` branch is basically just a snapshot of the latest stable release. All development should be done in dedicated branches. **Do not submit PRs against the `master` branch.**
36 |
37 | - Checkout a topic branch from the relevant branch, e.g. `dev`, and merge back against that branch.
38 |
39 | - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
40 |
41 | - If adding new feature:
42 | - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it green-lighted before working on it.
43 |
44 | - If fixing a bug:
45 | - If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.
46 | - Provide detailed description of the bug in the PR. Live demo preferred.
47 |
48 | ## Development Setup
49 |
50 | You will need [Node.js](http://nodejs.org) **version 8.9+** along [Yarn](https://yarnpkg.com/) or [NPM](https://docs.npmjs.com/getting-started/installing-node). Read `package.json` and take notice of the scripts you can use.
51 |
52 | After cloning the repo, run:
53 |
54 | ``` bash
55 | $ yarn # or: npm install
56 | ```
57 |
58 | ## Project Structure
59 |
60 | - **`bin`**: executables
61 |
62 | - **`quasar`**: entry point for CLI
63 |
64 | - **`quasar-*`**: entry point for CLI command
65 |
66 | - **`lib`**: core
67 |
68 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .Thumbs.db
3 | node_modules/
4 | ssl-server.pem
5 | npm-debug.log
6 | *.sublime*
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .editorconfig
2 | .github
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Quasar CLI
4 | > CLI for Quasar Framework. Start a project, build it, optimize it.
5 |
6 |
7 |
8 | # Quasar Framework
9 | > Build responsive Single Page Apps, **SSR Apps**, PWAs, Hybrid Mobile Apps and Electron Apps, all using the same codebase!, powered with Vue.
10 |
11 |
12 |
13 | ## Supporting Quasar
14 | Quasar Framework is an MIT-licensed open source project. Its ongoing development is made possible thanks to the support by these awesome [backers](https://github.com/rstoenescu/quasar-framework/blob/dev/backers.md). If you'd like to join them, check out [Quasar Framework's Patreon campaign](https://www.patreon.com/quasarframework).
15 |
16 | ## Quickstart
17 |
18 | ### Installing
19 |
20 | `$ npm install -g quasar-cli`
21 |
22 | ### TODO Command overview
23 |
24 | Display list of commands:
25 |
26 | `$ quasar`
27 |
28 | - `init` - create app from template
29 | - `dev` - run dev server for your app
30 | - `build` - build for production
31 | - `clean` - clean build assets
32 | - `new` - create app assets (component, pages, layouts, store module)
33 | - `serve` - start a live reload HTTP server on a folder (like `/dist`)
34 |
35 | ### Help
36 |
37 | `$ quasar [command name] --help`
38 |
39 |
80 |
81 | ## Documentation
82 |
83 | Head on to the Quasar Framework official website for help on CLI commands: [http://quasar-framework.org](http://quasar-framework.org/guide/quasar-cli.html)
84 |
85 | ## Community Forum
86 |
87 | Head on to the official community forum: [http://forum.quasar-framework.org](http://forum.quasar-framework.org)
88 |
89 | ## Quasar Repositories
90 |
91 | * [Quasar Framework](https://github.com/rstoenescu/quasar-framework)
92 | * [Quasar CLI](https://github.com/rstoenescu/quasar-cli)
93 | * [Quasar Play App](https://github.com/rstoenescu/quasar-play)
94 |
95 | ## Contributing
96 |
97 | I'm excited if you want to contribute to Quasar under any form (report bugs, write a plugin, fix an issue, write a new feature).
98 |
99 | ### Issue Reporting Guidelines
100 |
101 | **Please use the appropriate Github repo to report issues. See "Related Components" above.** For example, a bug related to CLI should be reported to the CLI repo, one related to build issues to Quasar Framework Templates repo and so on.
102 |
103 | - The issue list of the repository is **exclusively** for bug reports and feature requests. For anything else please use the [Community Forum](http://forum.quasar-framework.org).
104 |
105 | - Try to search for your issue, it may have already been fixed in the development branch or it may have a resolution.
106 |
107 | - Check if the issue is reproducible with the latest stable version of Quasar. If you are using a pre-release, please indicate the specific version you are using.
108 |
109 | - It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed.
110 |
111 | - If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
112 |
113 | Read more [here](http://quasar-framework.org/guide/contributing.html).
114 |
115 | ## License
116 |
117 | Copyright (c) 2016-present Razvan Stoenescu
118 |
119 | [MIT License](http://en.wikipedia.org/wiki/MIT_License)
120 |
--------------------------------------------------------------------------------
/assets/logo.art:
--------------------------------------------------------------------------------
1 | ___
2 | / _ \ _ _ __ _ ___ __ _ _ __
3 | | | | | | | |/ _` / __|/ _` | '__|
4 | | |_| | |_| | (_| \__ \ (_| | |
5 | \__\_\\__,_|\__,_|___/\__,_|_|
6 |
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/quasar-build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const parseArgs = require('minimist')
4 |
5 | const argv = parseArgs(process.argv.slice(2), {
6 | alias: {
7 | t: 'theme',
8 | m: 'mode',
9 | T: 'target',
10 | A: 'arch',
11 | b: 'bundler',
12 | d: 'debug',
13 | h: 'help'
14 | },
15 | boolean: ['h', 'd'],
16 | string: ['t', 'm', 'T'],
17 | default: {
18 | t: 'mat',
19 | m: 'spa'
20 | }
21 | })
22 |
23 | if (argv.help) {
24 | console.log(`
25 | Description
26 | Builds distributables of your app.
27 | Usage
28 | $ quasar build -p
29 | Options
30 | --theme, -t App theme (default: mat)
31 | --mode, -m App mode [spa|ssr|pwa|cordova|electron] (default: spa)
32 | --target, -T App target
33 | - Cordova (default: all installed)
34 | [android|ios|blackberry10|browser|osx|ubuntu|webos|windows]
35 | - Electron with default "electron-packager" bundler (default: yours)
36 | [darwin|win32|linux|mas|all]
37 | - Electron with "electron-builder" bundler (default: yours)
38 | [darwin|mac|win32|win|linux|all]
39 | --debug, -d Build for debugging purposes
40 | --help, -h Displays this message
41 |
42 | ONLY for Electron mode:
43 | --bundler, -b Bundler (electron-packager or electron-builder)
44 | [packager|builder]
45 | --arch, -A App architecture (default: yours)
46 | - with default "electron-packager" bundler:
47 | [ia32|x64|armv7l|arm64|mips64el|all]
48 | - with "electron-builder" bundler:
49 | [ia32|x64|armv7l|arm64|all]
50 | `)
51 | process.exit(0)
52 | }
53 |
54 | const chalk = require('chalk')
55 |
56 | const
57 | logger = require('../lib/helpers/logger'),
58 | log = logger('app:build'),
59 | warn = logger('app:build', 'red'),
60 | banner = require('../lib/helpers/banner')
61 |
62 | require('../lib/helpers/ensure-argv')(argv, 'build')
63 | banner(argv, 'build')
64 |
65 | if (argv.mode !== 'spa') {
66 | require('../lib/mode/install-missing')(argv.mode, argv.target)
67 | }
68 |
69 | const
70 | path = require('path'),
71 | webpack = require('webpack')
72 |
73 | const
74 | QuasarConfig = require('../lib/quasar-config'),
75 | Generator = require('../lib/generator'),
76 | artifacts = require('../lib/artifacts'),
77 | ensureDeps = require('../lib/helpers/ensure-deps')
78 |
79 | function parseWebpackConfig (webpackConfig, mode) {
80 | if (mode === 'ssr') {
81 | return [ webpackConfig.server, webpackConfig.client ]
82 | }
83 | if (mode === 'electron') {
84 | return [ webpackConfig.renderer, webpackConfig.main ]
85 | }
86 | return webpackConfig
87 | }
88 |
89 | function finalize (mode, quasarConfig) {
90 | if (mode === 'cordova') {
91 | return require('../lib/cordova').build(quasarConfig)
92 | }
93 | if (mode === 'electron') {
94 | return require('../lib/electron').build(quasarConfig)
95 | }
96 |
97 | return Promise.resolve()
98 | }
99 |
100 | async function build () {
101 | const quasarConfig = new QuasarConfig({
102 | theme: argv.theme,
103 | mode: argv.mode,
104 | target: argv.target,
105 | arch: argv.arch,
106 | bundler: argv.bundler,
107 | debug: argv.debug,
108 | prod: true
109 | })
110 |
111 | try {
112 | await quasarConfig.prepare()
113 | }
114 | catch (e) {
115 | console.log(e)
116 | warn(`⚠️ [FAIL] quasar.conf.js has JS errors`)
117 | process.exit(1)
118 | }
119 |
120 | quasarConfig.compile()
121 |
122 | const
123 | generator = new Generator(quasarConfig),
124 | webpackConfig = quasarConfig.getWebpackConfig(),
125 | buildConfig = quasarConfig.getBuildConfig(),
126 | mode = argv.mode.toUpperCase(),
127 | outputFolder = buildConfig.build.packagedElectronDist || buildConfig.build.distDir
128 |
129 | artifacts.clean(outputFolder)
130 | generator.prepare()
131 | generator.build()
132 |
133 | log(chalk.bold(`Building...`))
134 |
135 | webpack(parseWebpackConfig(webpackConfig, argv.mode), (err, stats) => {
136 | if (err) { throw err }
137 |
138 | artifacts.add(outputFolder)
139 |
140 | statsArray = stats.stats || [ stats ]
141 | statsArray.forEach(stat => {
142 | process.stdout.write('\n\n' + stat.toString({
143 | colors: true,
144 | performance: false,
145 | hash: false,
146 | assets: true,
147 | chunks: false,
148 | chunkModules: false,
149 | chunkOrigins: false,
150 | modules: false,
151 | nestedModules: false,
152 | moduleAssets: false,
153 | children: false
154 | }) + '\n\n')
155 | })
156 |
157 | statsArray.forEach(stat => {
158 | if (stat.hasErrors()) {
159 | warn()
160 | warn(chalk.red('[FAIL] Build failed with errors. Check log above.'))
161 | warn()
162 |
163 | process.exit(1)
164 | }
165 | })
166 |
167 | finalize(argv.mode, quasarConfig).then(() => {
168 | banner(argv, 'build', {
169 | outputFolder: argv.mode === 'cordova'
170 | ? path.join(outputFolder, '..')
171 | : outputFolder
172 | })
173 | })
174 | })
175 | }
176 |
177 | ensureDeps()
178 | build()
179 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/quasar-dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const
4 | parseArgs = require('minimist'),
5 | chalk = require('chalk')
6 |
7 | const
8 | logger = require('../lib/helpers/logger'),
9 | log = logger('app:dev'),
10 | warn = logger('app:dev', 'red'),
11 | appPaths = require('../lib/app-paths')
12 |
13 | const argv = parseArgs(process.argv.slice(2), {
14 | alias: {
15 | t: 'theme',
16 | m: 'mode',
17 | T: 'target', // cordova-mode only
18 | e: 'emulator', // cordova-mode only
19 | p: 'port',
20 | H: 'hostname',
21 | h: 'help'
22 | },
23 | boolean: ['h'],
24 | string: ['t', 'm', 'T', 'H'],
25 | default: {
26 | t: 'mat',
27 | m: 'spa'
28 | }
29 | })
30 |
31 | if (argv.help) {
32 | console.log(`
33 | Description
34 | Starts the app in development mode (hot-code reloading, error
35 | reporting, etc)
36 | Usage
37 | $ quasar dev -p
38 | Options
39 | --theme, -t App theme (default: mat)
40 | --mode, -m App mode [spa|ssr|pwa|cordova|electron] (default: spa)
41 | --port, -p A port number on which to start the application
42 | --hostname, -H A hostname to use for serving the application
43 | --help, -h Displays this message
44 |
45 | Only for Cordova mode:
46 | --target, -T (required) App target
47 | [android|ios|blackberry10|browser|osx|ubuntu|webos|windows]
48 | --emulator, -e (optional) Emulator name
49 | Example: iPhone-7, iPhone-X
50 | `)
51 | process.exit(0)
52 | }
53 |
54 | require('../lib/helpers/ensure-argv')(argv, 'dev')
55 | require('../lib/helpers/banner')(argv, 'dev')
56 |
57 | if (argv.mode !== 'spa') {
58 | require('../lib/mode/install-missing')(argv.mode, argv.target)
59 | }
60 |
61 | const
62 | ensureDeps = require('../lib/helpers/ensure-deps'),
63 | findPort = require('../lib/helpers/net').findClosestOpenPort
64 |
65 | async function parseAddress ({ host, port }) {
66 | if (this.chosenHost) {
67 | host = this.chosenHost
68 | }
69 | else {
70 | if (host && ['localhost', '127.0.0.1', '::1'].includes(host.toLowerCase())) {
71 | host = '0.0.0.0'
72 | }
73 | if (argv.mode === 'cordova' && (!host || host === '0.0.0.0')) {
74 | const getExternalIP = require('../lib/helpers/get-external-ip')
75 | host = await getExternalIP()
76 | this.chosenHost = host
77 | }
78 | }
79 |
80 | log(`Checking listening address availability (${host}:${port})...`)
81 |
82 | try {
83 | const openPort = await findPort(port, host)
84 | if (port !== openPort) {
85 | warn()
86 | warn(`Setting port to closest one available: ${openPort}`)
87 | warn()
88 |
89 | port = openPort
90 | }
91 | }
92 | catch (e) {
93 | warn()
94 |
95 | if (e.message === 'ERROR_NETWORK_PORT_NOT_AVAIL') {
96 | warn(`⚠️ Could not find an open port. Please configure a lower one to start searching with.`)
97 | }
98 | else if (e.message === 'ERROR_NETWORK_ADDRESS_NOT_AVAIL') {
99 | warn(`⚠️ Invalid host specified. No network address matches. Please specify another one.`)
100 | }
101 | else {
102 | warn(`⚠️ Unknown network error occured`)
103 | console.log(e)
104 | }
105 |
106 | warn()
107 |
108 | if (!this.running) {
109 | process.exit(1)
110 | }
111 |
112 | return null
113 | }
114 |
115 | this.running = true
116 | return { host, port }
117 | }
118 |
119 | async function goLive () {
120 | ensureDeps()
121 |
122 | const
123 | DevServer = require('../lib/dev-server'),
124 | QuasarConfig = require('../lib/quasar-config'),
125 | Generator = require('../lib/generator')
126 |
127 | const
128 | quasarConfig = new QuasarConfig({
129 | theme: argv.theme,
130 | mode: argv.mode,
131 | target: argv.target,
132 | emulator: argv.emulator,
133 | port: argv.port,
134 | host: argv.hostname,
135 | dev: true,
136 | onAddress: parseAddress,
137 | onBuildChange () {
138 | log(`Build changes detected. Rebuilding app...`)
139 | dev = dev.then(startDev)
140 | },
141 | onAppChange () {
142 | log(`App changes detected. Updating app...`)
143 | generator.build()
144 | }
145 | })
146 |
147 | try {
148 | await quasarConfig.prepare()
149 | }
150 | catch (e) {
151 | console.log(e)
152 | warn(`[FAIL] quasar.conf.js has JS errors`)
153 | process.exit(1)
154 | }
155 |
156 | quasarConfig.compile()
157 |
158 | const
159 | generator = new Generator(quasarConfig),
160 | Cordova = argv.mode === 'cordova' ? require('../lib/cordova') : false,
161 | Electron = argv.mode === 'electron' ? require('../lib/electron') : false
162 |
163 | generator.prepare()
164 |
165 | function startDev (oldDevServer) {
166 | let devServer
167 |
168 | return Promise.resolve()
169 | .then(() => devServer = new DevServer(quasarConfig))
170 | .then(() => oldDevServer && oldDevServer.stop())
171 | .then(() => generator.build()) // Update generated files
172 | .then(() => devServer.listen()) // Start listening
173 | .then(() => Electron && Electron.run(quasarConfig))
174 | .then(() => Cordova && Cordova.run(quasarConfig))
175 | .then(() => devServer) // Pass new builder to watch chain
176 | }
177 |
178 | let dev = startDev()
179 | }
180 |
181 | goLive()
182 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/quasar-info:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const
4 | parseArgs = require('minimist'),
5 | chalk = require('chalk')
6 |
7 | const
8 | appPaths = require('../lib/app-paths')
9 |
10 | const argv = parseArgs(process.argv.slice(2), {
11 | alias: {
12 | h: 'help'
13 | },
14 | boolean: ['h']
15 | })
16 |
17 | if (argv.help) {
18 | console.log(`
19 | Description
20 | Displays information about your machine and your Quasar App.
21 | Usage
22 | $ quasar info
23 | Options
24 | --help, -h Displays this message
25 | `)
26 | process.exit(0)
27 | }
28 |
29 | const
30 | os = require('os'),
31 | spawn = require('cross-spawn').sync
32 |
33 | function getSpawnOutput (command) {
34 | try {
35 | const child = spawn(command, ['--version'])
36 | return child.status === 0
37 | ? chalk.green(String(child.output[1]).trim())
38 | : chalk.red('Not installed')
39 | }
40 | catch (err) {
41 | return chalk.red('Not installed')
42 | }
43 | }
44 |
45 | function safePkgInfo (pkg) {
46 | try {
47 | const content = require(appPaths.resolve.app(`node_modules/${pkg}/package.json`))
48 | return {
49 | key: ` ${String(content.name).trim()}`,
50 | value: `${chalk.green(String(content.version).trim())}${content.description ? `\t(${content.description})` : ''}`
51 | }
52 | }
53 | catch (err) {
54 | return {
55 | key: ` ${pkg}`,
56 | value: chalk.red('Not installed')
57 | }
58 | }
59 | }
60 |
61 | const
62 | getExternalIPs = require('../lib/helpers/net').getExternalNetworkInterface,
63 | output = [
64 | { key: 'Operating System', value: chalk.green(`${os.type()}(${os.release()}) - ${os.platform()}/${os.arch()}`), section: true },
65 | { key: 'NodeJs', value: chalk.green(process.version.slice(1)) },
66 | { key: 'Global packages', section: true },
67 | { key: ' NPM', value: getSpawnOutput('npm') },
68 | { key: ' yarn', value: getSpawnOutput('yarn') },
69 | { key: ' quasar-cli', value: getSpawnOutput('quasar') },
70 | { key: ' vue-cli', value: getSpawnOutput('vue') },
71 | { key: ' cordova', value: getSpawnOutput('cordova') },
72 | { key: 'Important local packages', section: true }
73 | ]
74 |
75 | ;[
76 | 'quasar-cli',
77 | 'quasar-framework',
78 | 'quasar-extras',
79 | 'vue',
80 | 'vue-router',
81 | 'vuex',
82 | 'electron',
83 | 'electron-packager',
84 | 'electron-builder',
85 | '@babel/core',
86 | 'webpack',
87 | 'webpack-dev-server',
88 | 'workbox-webpack-plugin',
89 | 'register-service-worker'
90 | ].forEach(pkg => output.push(safePkgInfo(pkg)))
91 |
92 | output.push(
93 | { key: 'Networking', section: true },
94 | { key: ' Host', value: chalk.green(os.hostname()) }
95 | )
96 | getExternalIPs().forEach(intf => {
97 | output.push({
98 | key: ` ${ intf.deviceName }`,
99 | value: chalk.green(intf.address)
100 | })
101 | })
102 |
103 | const spaces = output.reduce((acc, v) => Math.max(acc, v.key.length), 0)
104 | console.log(
105 | output
106 | .map(m => `${m.section ? '\n' : ''}${ m.key }${' '.repeat(spaces - m.key.length)}\t${ m.value === undefined ? '' : m.value }`).join('\n')
107 | )
108 | console.log()
109 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/quasar-new:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const parseArgs = require('minimist')
4 |
5 | const argv = parseArgs(process.argv.slice(2), {
6 | alias: {
7 | h: 'help'
8 | },
9 | boolean: ['h']
10 | })
11 |
12 | function showHelp () {
13 | console.log(`
14 | Description
15 | Quickly scaffold a page/layout/component/store module.
16 |
17 | Usage
18 | $ quasar new [p|page]
19 | $ quasar new [l|layout]
20 | $ quasar new [c|component]
21 | $ quasar new plugin
22 | $ quasar new [s|store]
23 |
24 | # Examples:
25 |
26 | # Create src/pages/MyNewPage.vue:
27 | $ quasar new p MyNewPage
28 |
29 | # Create src/pages/MyNewPage.vue and src/pages/OtherPage.vue:
30 | $ quasar new p MyNewPage OtherPage
31 |
32 | # Create src/layouts/shop/Checkout.vue
33 | $ quasar new layout shop/Checkout.vue
34 |
35 | Options
36 | --help, -h Displays this message
37 | `)
38 | process.exit(0)
39 | }
40 |
41 | if (argv.help) {
42 | showHelp()
43 | }
44 |
45 | const
46 | logger = require('../lib/helpers/logger'),
47 | log = logger('app:new'),
48 | warn = logger('app:new', 'red'),
49 | appPaths = require('../lib/app-paths'),
50 | path = require('path'),
51 | fs = require('fs'),
52 | fse = require('fs-extra')
53 |
54 | if (argv._.length < 2) {
55 | console.log()
56 | warn(`⚠️ Wrong number of parameters (${argv._.length}).`)
57 | showHelp()
58 | process.exit(1)
59 | }
60 |
61 | let [ type, ...names ] = argv._
62 |
63 | if (!['p', 'page', 'l', 'layout', 'c', 'component', 's', 'store', 'plugin'].includes(type)) {
64 | console.log()
65 | warn(`⚠️ Invalid asset type: ${type}`)
66 | showHelp()
67 | }
68 |
69 | if (type.length === 1) {
70 | const fullCmd = {
71 | p: 'page',
72 | l: 'layout',
73 | c: 'component',
74 | s: 'store'
75 | }
76 | type = fullCmd[type]
77 | }
78 |
79 | function getPaths (asset, names) {
80 | return names.map(name => {
81 | const
82 | hasExtension = !asset.ext || (asset.ext && name.endsWith(asset.ext)),
83 | ext = hasExtension ? '' : asset.ext
84 |
85 | return appPaths.resolve.src(path.join(asset.folder, name + ext))
86 | })
87 | }
88 |
89 | function createFile (asset, file) {
90 | const relativePath = path.relative(appPaths.appDir, file)
91 |
92 | if (fs.existsSync(file)) {
93 | warn(`[SKIPPED] ${relativePath} already exists.`)
94 | console.log()
95 | return
96 | }
97 |
98 | fse.mkdirp(path.dirname(file))
99 | fse.copy(
100 | appPaths.resolve.cli(path.join('templates/app', type + (asset.ext || ''))),
101 | file,
102 | err => {
103 |
104 | if (err) {
105 | console.warn(err)
106 | warn('[FAIL] Could not generate ${relativePath}.')
107 | return
108 | }
109 |
110 | log(`Generated ${type}: ${relativePath}`)
111 | if (asset.reference) {
112 | log(`Make sure to reference it in ${asset.reference}`)
113 | }
114 | log()
115 | }
116 | )
117 | }
118 |
119 | const
120 | mapping = {
121 | page: {
122 | folder: 'pages',
123 | ext: '.vue',
124 | reference: 'src/router/routes.js'
125 | },
126 | layout: {
127 | folder: 'layouts',
128 | ext: '.vue',
129 | reference: 'src/router/routes.js'
130 | },
131 | component: {
132 | folder: 'components',
133 | ext: '.vue'
134 | },
135 | store: {
136 | folder: 'store',
137 | reference: 'src/store/index.js'
138 | },
139 | plugin: {
140 | folder: 'plugins',
141 | ext: '.js',
142 | reference: 'quasar.conf.js > plugins'
143 | }
144 | },
145 | asset = mapping[type]
146 |
147 | const filesToCreate = getPaths(asset, names)
148 |
149 | filesToCreate.forEach(file => {
150 | createFile(asset, file)
151 | })
152 |
--------------------------------------------------------------------------------
/bin/quasar-serve:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const parseArgs = require('minimist')
4 |
5 | const argv = parseArgs(process.argv.slice(2), {
6 | alias: {
7 | p: 'port',
8 | H: 'hostname',
9 | g: 'gzip',
10 | s: 'silent',
11 | colors: 'colors',
12 | o: 'open',
13 | c: 'cache',
14 | m: 'micro',
15 | history: 'history',
16 | https: 'https',
17 | C: 'cert',
18 | K: 'key',
19 | P: 'proxy',
20 | h: 'help'
21 | },
22 | boolean: ['g', 'https', 'colors', 'S', 'history', 'h'],
23 | string: ['H', 'C', 'K'],
24 | default: {
25 | p: process.env.PORT || 4000,
26 | H: process.env.HOSTNAME || '0.0.0.0',
27 | g: true,
28 | c: 24 * 60 * 60,
29 | m: 1,
30 | colors: true
31 | }
32 | })
33 |
34 | if (argv.help) {
35 | console.log(`
36 | Description
37 | Start a HTTP(S) server on a folder.
38 |
39 | Usage
40 | $ quasar serve [path]
41 | $ quasar serve . # serve current folder
42 |
43 | If you serve a SSR folder built with the CLI then
44 | control is yielded to /index.js and params have no effect.
45 |
46 | Options
47 | --port, -p Port to use (default: 4000)
48 | --hostname, -H Address to use (default: 0.0.0.0)
49 | --gzip, -g Compress content (default: true)
50 | --silent, -s Supress log message
51 | --colors Log messages with colors (default: true)
52 | --open, -o Open browser window after starting
53 | --cache, -c Cache time (max-age) in seconds;
54 | Does not apply to /service-worker.js
55 | (default: 86400 - 24 hours)
56 | --micro, -m Use micro-cache (default: 1 second)
57 | --history Use history api fallback;
58 | All requests fallback to index.html
59 | --https Enable HTTPS
60 | --cert, -C [path] Path to SSL cert file (Optional)
61 | --key, -K [path] Path to SSL key file (Optional)
62 | --proxy Proxy specific requests defined in file;
63 | File must export Array ({ path, rule })
64 | See example below. "rule" is defined at:
65 | https://github.com/chimurai/http-proxy-middleware
66 | --help, -h Displays this message
67 |
68 | Proxy file example
69 | module.exports = [
70 | {
71 | path: '/api',
72 | rule: { target: 'http://www.example.org' }
73 | }
74 | ]
75 | --> will be transformed into app.use(path, httpProxyMiddleware(rule))
76 | `)
77 | process.exit(0)
78 | }
79 |
80 | const
81 | fs = require('fs'),
82 | path = require('path')
83 |
84 | const root = getAbsolutePath(argv._[0] || '.')
85 | const resolve = p => path.resolve(root, p)
86 |
87 | function getAbsolutePath (pathParam) {
88 | return path.isAbsolute(pathParam)
89 | ? pathParam
90 | : path.join(process.cwd(), pathParam)
91 | }
92 |
93 | const
94 | pkgFile = resolve('package.json'),
95 | indexFile = resolve('index.js')
96 |
97 | let ssrDetected = false
98 |
99 | if (fs.existsSync(pkgFile) && fs.existsSync(indexFile)) {
100 | const pkg = require(pkgFile)
101 | if (pkg.quasar && pkg.quasar.ssr) {
102 | console.log('Quasar SSR folder detected.')
103 | console.log('Yielding control to its own webserver.')
104 | console.log()
105 | ssrDetected = true
106 | require(indexFile)
107 | }
108 | }
109 |
110 | if (ssrDetected === false) {
111 | let green, grey, red
112 |
113 | if (argv.colors) {
114 | const chalk = require('chalk')
115 | green = chalk.green
116 | grey = chalk.grey
117 | red = chalk.red
118 | }
119 | else {
120 | green = grey = red = text => text
121 | }
122 |
123 | const
124 | express = require('express'),
125 | microCacheSeconds = argv.micro
126 | ? parseInt(argv.micro, 10)
127 | : false
128 |
129 | function serve (path, cache) {
130 | return express.static(resolve(path), {
131 | maxAge: cache ? parseInt(argv.cache, 10) * 1000 : 0
132 | })
133 | }
134 |
135 | const app = express()
136 |
137 | if (!argv.silent) {
138 | app.get('*', (req, res, next) => {
139 | console.log(
140 | `GET ${green(req.url)} ${grey('[' + req.ip + ']')} ${new Date()}`
141 | )
142 | next()
143 | })
144 | }
145 |
146 | if (argv.gzip) {
147 | const compression = require('compression')
148 | app.use(compression({ threshold: 0 }))
149 | }
150 |
151 | const serviceWorkerFile = resolve('service-worker.js')
152 | if (fs.existsSync(serviceWorkerFile)) {
153 | app.use('/service-worker.js', serve('service-worker.js'))
154 | }
155 |
156 | if (argv.history) {
157 | const history = require('connect-history-api-fallback')
158 | app.use(history())
159 | }
160 |
161 | app.use('/', serve('.', true))
162 |
163 | if (microCacheSeconds) {
164 | const microcache = require('route-cache')
165 | app.use(
166 | microcache.cacheSeconds(
167 | microCacheSeconds,
168 | req => req.originalUrl
169 | )
170 | )
171 | }
172 |
173 | if (argv.proxy) {
174 | let file = argv.proxy = getAbsolutePath(argv.proxy)
175 | if (!fs.existsSync(file)) {
176 | console.error('Proxy definition file not found! ' + file)
177 | process.exit(1)
178 | }
179 | file = require(file)
180 |
181 | const proxy = require('http-proxy-middleware')
182 | file.forEach(entry => {
183 | app.use(entry.path, proxy(entry.rule))
184 | })
185 | }
186 |
187 | app.get('*', (req, res) => {
188 | res.setHeader('Content-Type', 'text/html')
189 | res.status(404).send('404 | Page Not Found')
190 | if (!argv.silent) {
191 | console.log(red(` 404 on ${req.url}`))
192 | }
193 | })
194 |
195 | function getHostname (host) {
196 | return host === '0.0.0.0'
197 | ? 'localhost'
198 | : host
199 | }
200 |
201 | getServer(app).listen(argv.port, argv.hostname, () => {
202 | const
203 | url = `http${argv.https ? 's' : ''}://${getHostname(argv.hostname)}:${argv.port}`,
204 | { version } = require('../package.json')
205 |
206 | const info = [
207 | ['Quasar CLI', `v${version}`],
208 | ['Listening at', url],
209 | ['Web server root', root],
210 | argv.https ? ['HTTPS', 'enabled'] : '',
211 | argv.gzip ? ['Gzip', 'enabled'] : '',
212 | ['Cache (max-age)', argv.cache || 'disabled'],
213 | microCacheSeconds ? ['Micro-cache', microCacheSeconds + 's'] : '',
214 | argv.history ? ['History mode', 'enabled'] : '',
215 | argv.proxy ? ['Proxy definitions', argv.proxy] : ''
216 | ]
217 | .filter(msg => msg)
218 | .map(msg => ' ' + msg[0].padEnd(20, '.') + ' ' + green(msg[1]))
219 |
220 | console.log('\n' + info.join('\n') + '\n')
221 |
222 | if (argv.open) {
223 | const isMinimalTerminal = require('../lib/helpers/is-minimal-terminal')
224 | if (!isMinimalTerminal) {
225 | const opn = require('opn')
226 | opn(url)
227 | }
228 | }
229 | })
230 |
231 | function getServer (app) {
232 | if (!argv.https) {
233 | return app
234 | }
235 |
236 | let fakeCert, key, cert
237 |
238 | if (argv.key && argv.cert) {
239 | key = getAbsolutePath(argv.key)
240 | cert = getAbsolutePath(argv.cert)
241 |
242 | if (fs.existsSync(key)) {
243 | key = fs.readFileSync(key)
244 | }
245 | else {
246 | console.error('SSL key file not found!' + key)
247 | process.exit(1)
248 | }
249 |
250 | if (fs.existsSync(cert)) {
251 | cert = fs.readFileSync(cert)
252 | }
253 | else {
254 | console.error('SSL cert file not found!' + cert)
255 | process.exit(1)
256 | }
257 | }
258 | else {
259 | // Use a self-signed certificate if no certificate was configured.
260 | // Cycle certs every 24 hours
261 | const certPath = path.join(__dirname, '../ssl-server.pem')
262 | let certExists = fs.existsSync(certPath)
263 |
264 | if (certExists) {
265 | const certStat = fs.statSync(certPath)
266 | const certTtl = 1000 * 60 * 60 * 24
267 | const now = new Date()
268 |
269 | // cert is more than 30 days old
270 | if ((now - certStat.ctime) / certTtl > 30) {
271 | console.log(' SSL Certificate is more than 30 days old. Removing.')
272 | const { removeSync } = require('fs-extra')
273 | removeSync(certPath)
274 | certExists = false
275 | }
276 | }
277 |
278 | if (!certExists) {
279 | console.log(' Generating self signed SSL Certificate...')
280 | console.log(' DO NOT use this self-signed certificate in production!')
281 |
282 | const selfsigned = require('selfsigned')
283 | const pems = selfsigned.generate(
284 | [{ name: 'commonName', value: 'localhost' }],
285 | {
286 | algorithm: 'sha256',
287 | days: 30,
288 | keySize: 2048,
289 | extensions: [{
290 | name: 'basicConstraints',
291 | cA: true
292 | }, {
293 | name: 'keyUsage',
294 | keyCertSign: true,
295 | digitalSignature: true,
296 | nonRepudiation: true,
297 | keyEncipherment: true,
298 | dataEncipherment: true
299 | }, {
300 | name: 'subjectAltName',
301 | altNames: [
302 | {
303 | // type 2 is DNS
304 | type: 2,
305 | value: 'localhost'
306 | },
307 | {
308 | type: 2,
309 | value: 'localhost.localdomain'
310 | },
311 | {
312 | type: 2,
313 | value: 'lvh.me'
314 | },
315 | {
316 | type: 2,
317 | value: '*.lvh.me'
318 | },
319 | {
320 | type: 2,
321 | value: '[::1]'
322 | },
323 | {
324 | // type 7 is IP
325 | type: 7,
326 | ip: '127.0.0.1'
327 | },
328 | {
329 | type: 7,
330 | ip: 'fe80::1'
331 | }
332 | ]
333 | }]
334 | }
335 | )
336 |
337 | try {
338 | fs.writeFileSync(certPath, pems.private + pems.cert, { encoding: 'utf-8' })
339 | }
340 | catch (err) {
341 | console.error(' Cannot write certificate file ' + certPath)
342 | console.error(' Aborting...')
343 | process.exit(1)
344 | }
345 | }
346 |
347 | fakeCert = fs.readFileSync(certPath)
348 | }
349 |
350 | return require('https').createServer({
351 | key: key || fakeCert,
352 | cert: cert || fakeCert
353 | }, app)
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/cordova/cordova-config.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | et = require('elementtree')
4 |
5 | const
6 | appPaths = require('../app-paths'),
7 | logger = require('../helpers/logger'),
8 | log = logger('app:cordova-conf')
9 | warn = logger('app:cordova-conf', 'red')
10 |
11 | const filePath = appPaths.resolve.cordova('config.xml')
12 |
13 | function setFields (root, cfg) {
14 | Object.keys(cfg).forEach(key => {
15 | const
16 | el = root.find(key),
17 | values = cfg[key],
18 | isObject = Object(values) === values
19 |
20 | if (!el) {
21 | if (isObject) {
22 | et.SubElement(root, key, values)
23 | }
24 | else {
25 | let entry = et.SubElement(root, key)
26 | entry.text = values
27 | }
28 | }
29 | else {
30 | if (isObject) {
31 | Object.keys(values).forEach(key => {
32 | el.set(key, values[key])
33 | })
34 | }
35 | else {
36 | el.text = values
37 | }
38 | }
39 | })
40 | }
41 |
42 | class CordovaConfig {
43 | prepare (cfg) {
44 | this.doc = et.parse(fs.readFileSync(filePath, 'utf-8'))
45 | this.pkg = require(appPaths.resolve.app('package.json'))
46 | this.APP_URL = cfg.build.APP_URL
47 |
48 | const root = this.doc.getroot()
49 |
50 | root.set('id', cfg.cordova.id || this.pkg.cordovaId || 'org.quasar.cordova.app')
51 | root.set('version', cfg.cordova.version || this.pkg.version)
52 |
53 | if (cfg.cordova.androidVersionCode) {
54 | root.set('android-versionCode', cfg.cordova.androidVersionCode)
55 | }
56 |
57 | setFields(root, {
58 | content: { src: this.APP_URL },
59 | description: cfg.cordova.description || this.pkg.description
60 | })
61 |
62 | if (this.APP_URL !== 'index.html' && !root.find(`allow-navigation[@href='${this.APP_URL}']`)) {
63 | et.SubElement(root, 'allow-navigation', { href: this.APP_URL })
64 |
65 | if (cfg.devServer.https && cfg.ctx.targetName === 'ios') {
66 | const node = root.find('name')
67 | if (node) {
68 | const filePath = appPaths.resolve.cordova(
69 | `platforms/ios/${node.text}/Classes/AppDelegate.m`
70 | )
71 |
72 | if (!fs.existsSync(filePath)) {
73 | warn()
74 | warn()
75 | warn()
76 | warn()
77 | warn(`AppDelegate.m not found. Your App will revoke the devserver's SSL certificate.`)
78 | warn(`Please report the cordova CLI version and cordova-ios package that you are using.`)
79 | warn(`Also, disable HTTPS from quasar.conf.js > devServer > https`)
80 | warn()
81 | warn()
82 | warn()
83 | warn()
84 | }
85 | else {
86 | this.iosDelegateFilePath = filePath
87 | this.iosDelegateOriginal = fs.readFileSync(this.iosDelegateFilePath, 'utf-8')
88 |
89 | // required for allowing devserver's SSL certificate on iOS
90 | if (this.iosDelegateOriginal.indexOf('allowsAnyHTTPSCertificateForHost') === -1) {
91 | this.iosDelegateNew = this.iosDelegateOriginal + `
92 |
93 | @implementation NSURLRequest(DataController)
94 | + (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
95 | {
96 | return YES;
97 | }
98 | @end
99 | `
100 | }
101 | }
102 | }
103 | }
104 | }
105 |
106 | this.__save()
107 | }
108 |
109 | reset () {
110 | if (!this.APP_URL || this.APP_URL === 'index.html') {
111 | return
112 | }
113 |
114 | const root = this.doc.getroot()
115 |
116 | root.find('content').set('src', 'index.html')
117 |
118 | const nav = root.find(`allow-navigation[@href='${this.APP_URL}']`)
119 | if (nav) {
120 | root.remove(nav)
121 | }
122 |
123 | if (this.iosDelegateOriginal && this.iosDelegateNew) {
124 | this.iosDelegateNew = this.iosDelegateOriginal
125 | }
126 |
127 | this.__save()
128 | }
129 |
130 | __save () {
131 | const content = this.doc.write({ indent: 4 })
132 | fs.writeFileSync(filePath, content, 'utf8')
133 | log('Updated Cordova config.xml')
134 |
135 | if (this.iosDelegateFilePath && this.iosDelegateNew) {
136 | fs.writeFileSync(this.iosDelegateFilePath, this.iosDelegateNew, 'utf8')
137 | log('Updated AppDelegate.m')
138 | }
139 | }
140 | }
141 |
142 | module.exports = CordovaConfig
143 |
--------------------------------------------------------------------------------
/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/dev-server.js:
--------------------------------------------------------------------------------
1 | const
2 | webpack = require('webpack'),
3 | WebpackDevServer = require('webpack-dev-server')
4 |
5 | const
6 | appPaths = require('./app-paths'),
7 | log = require('./helpers/logger')('app:dev-server')
8 |
9 | let alreadyNotified = false
10 |
11 | function openBrowser (url) {
12 | const opn = require('opn')
13 | opn(url)
14 | }
15 |
16 | module.exports = class DevServer {
17 | constructor (quasarConfig) {
18 | this.quasarConfig = quasarConfig
19 | }
20 |
21 | async listen () {
22 | const
23 | webpackConfig = this.quasarConfig.getWebpackConfig(),
24 | cfg = this.quasarConfig.getBuildConfig()
25 |
26 | log(`Booting up...`)
27 | log()
28 |
29 | return new Promise(resolve => (
30 | cfg.ctx.mode.ssr
31 | ? this.listenSSR(webpackConfig, cfg, resolve)
32 | : this.listenCSR(webpackConfig, cfg, resolve)
33 | ))
34 | }
35 |
36 | listenCSR (webpackConfig, cfg, resolve) {
37 | const compiler = webpack(webpackConfig.renderer || webpackConfig)
38 |
39 | compiler.hooks.done.tap('done-compiling', compiler => {
40 | if (this.__started) { return }
41 |
42 | // start dev server if there are no errors
43 | if (compiler.compilation.errors && compiler.compilation.errors.length > 0) {
44 | return
45 | }
46 |
47 | this.__started = true
48 |
49 | server.listen(cfg.devServer.port, cfg.devServer.host, () => {
50 | resolve()
51 |
52 | if (alreadyNotified) { return }
53 | alreadyNotified = true
54 |
55 | if (cfg.devServer.open && ['spa', 'pwa'].includes(cfg.ctx.modeName)) {
56 | openBrowser(cfg.build.APP_URL)
57 | }
58 | })
59 | })
60 |
61 | // start building & launch server
62 | const server = new WebpackDevServer(compiler, cfg.devServer)
63 |
64 | this.__cleanup = () => {
65 | this.__cleanup = null
66 | return new Promise(resolve => {
67 | server.close(resolve)
68 | })
69 | }
70 | }
71 |
72 | listenSSR (webpackConfig, cfg, resolve) {
73 | const
74 | fs = require('fs'),
75 | LRU = require('lru-cache'),
76 | express = require('express'),
77 | chokidar = require('chokidar'),
78 | { createBundleRenderer } = require('vue-server-renderer'),
79 | ouchInstance = require('./helpers/cli-error-handling').getOuchInstance()
80 |
81 | let renderer
82 |
83 | function createRenderer (bundle, options) {
84 | // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
85 | return createBundleRenderer(bundle, Object.assign(options, {
86 | // for component caching
87 | cache: LRU({
88 | max: 1000,
89 | maxAge: 1000 * 60 * 15
90 | }),
91 | // recommended for performance
92 | runInNewContext: false
93 | }))
94 | }
95 |
96 | function render (req, res) {
97 | const startTime = Date.now()
98 |
99 | res.setHeader('Content-Type', 'text/html')
100 |
101 | const handleError = err => {
102 | if (err.url) {
103 | res.redirect(err.url)
104 | }
105 | else if (err.code === 404) {
106 | res.status(404).send('404 | Page Not Found')
107 | }
108 | else {
109 | ouchInstance.handleException(err, req, res, output => {
110 | console.error(`${req.url} -> error during render`)
111 | console.error(err.stack)
112 | })
113 | }
114 | }
115 |
116 | const context = {
117 | url: req.url,
118 | req,
119 | res
120 | }
121 |
122 | renderer.renderToString(context, (err, html) => {
123 | if (err) {
124 | handleError(err)
125 | return
126 | }
127 | if (cfg.__meta) {
128 | html = context.$getMetaHTML(html)
129 | }
130 | console.log(`${req.url} -> request took: ${Date.now() - startTime}ms`)
131 | res.send(html)
132 | })
133 | }
134 |
135 | let
136 | bundle,
137 | template,
138 | clientManifest,
139 | pwa
140 |
141 | let ready
142 | const readyPromise = new Promise(r => { ready = r })
143 | function update () {
144 | if (bundle && clientManifest) {
145 | renderer = createRenderer(bundle, {
146 | template,
147 | clientManifest,
148 | basedir: appPaths.resolve.app('.')
149 | })
150 | ready()
151 | }
152 | }
153 |
154 | // read template from disk and watch
155 | const
156 | { getIndexHtml } = require('./ssr/html-template'),
157 | templatePath = appPaths.resolve.app(cfg.sourceFiles.indexHtmlTemplate)
158 |
159 | function getTemplate () {
160 | return getIndexHtml(fs.readFileSync(templatePath, 'utf-8'), cfg)
161 | }
162 |
163 | template = getTemplate()
164 | const htmlWatcher = chokidar.watch(templatePath).on('change', () => {
165 | template = getTemplate()
166 | console.log('index.template.html template updated.')
167 | update()
168 | })
169 |
170 | const
171 | serverCompiler = webpack(webpackConfig.server),
172 | clientCompiler = webpack(webpackConfig.client)
173 |
174 | serverCompiler.hooks.done.tapAsync('done-compiling', ({ compilation: { errors, warnings, assets }}, cb) => {
175 | errors.forEach(err => console.error(err))
176 | warnings.forEach(err => console.warn(err))
177 |
178 | if (errors.length > 0) {
179 | cb()
180 | return
181 | }
182 |
183 | bundle = JSON.parse(assets['../vue-ssr-server-bundle.json'].source())
184 | update()
185 |
186 | cb()
187 | })
188 |
189 | clientCompiler.hooks.done.tapAsync('done-compiling', ({ compilation: { errors, warnings, assets }}, cb) => {
190 | errors.forEach(err => console.error(err))
191 | warnings.forEach(err => console.warn(err))
192 |
193 | if (errors.length > 0) {
194 | cb()
195 | return
196 | }
197 |
198 | if (cfg.ctx.mode.pwa) {
199 | pwa = {
200 | manifest: assets['manifest.json'].source(),
201 | serviceWorker: assets['service-worker.js'].source()
202 | }
203 | }
204 |
205 | clientManifest = JSON.parse(assets['../vue-ssr-client-manifest.json'].source())
206 | update()
207 |
208 | cb()
209 | })
210 |
211 | const serverCompilerWatcher = serverCompiler.watch({}, () => {})
212 |
213 | // start building & launch server
214 | const server = new WebpackDevServer(clientCompiler, Object.assign(
215 | {
216 | after: app => {
217 | if (cfg.ctx.mode.pwa) {
218 | app.use('/manifest.json', (req, res) => {
219 | res.setHeader('Content-Type', 'application/json')
220 | res.send(pwa.manifest)
221 | })
222 | app.use('/service-worker.js', (req, res) => {
223 | res.setHeader('Content-Type', 'text/javascript')
224 | res.send(pwa.serviceWorker)
225 | })
226 | }
227 |
228 | app.use('/statics', express.static(appPaths.resolve.src('statics'), {
229 | maxAge: 0
230 | }))
231 |
232 | cfg.__ssrExtension.extendApp({ app })
233 |
234 | app.get('*', render)
235 | }
236 | },
237 | cfg.devServer
238 | ))
239 |
240 | readyPromise.then(() => {
241 | server.listen(cfg.devServer.port, cfg.devServer.host, () => {
242 | resolve()
243 | if (cfg.devServer.open) {
244 | openBrowser(cfg.build.APP_URL)
245 | }
246 | })
247 | })
248 |
249 | this.__cleanup = () => {
250 | this.__cleanup = null
251 | htmlWatcher.close()
252 | return Promise.all([
253 | new Promise(resolve => { server.close(resolve) }),
254 | new Promise(resolve => { serverCompilerWatcher.close(resolve) })
255 | ])
256 | }
257 | }
258 |
259 | stop () {
260 | if (this.__cleanup) {
261 | log(`Shutting down`)
262 | return this.__cleanup()
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/electron/index.js:
--------------------------------------------------------------------------------
1 | const
2 | spawn = require('../helpers/spawn'),
3 | webpack = require('webpack'),
4 | logger = require('../helpers/logger'),
5 | log = logger('app:electron'),
6 | warn = logger('app:electron', 'red'),
7 | path = require('path'),
8 | fse = require('fs-extra'),
9 | appPaths = require('../app-paths'),
10 | nodePackager = require('../helpers/node-packager')
11 |
12 | class ElectronRunner {
13 | constructor () {
14 | this.pid = 0
15 | this.watcher = null
16 | }
17 |
18 | async run (quasarConfig) {
19 | const url = quasarConfig.getBuildConfig().build.APP_URL
20 |
21 | if (this.pid) {
22 | if (this.url !== url) {
23 | await this.stop()
24 | }
25 | else {
26 | return
27 | }
28 | }
29 |
30 | this.url = url
31 |
32 | const compiler = webpack(quasarConfig.getWebpackConfig().main)
33 |
34 | return new Promise((resolve, reject) => {
35 | log(`Building main Electron process...`)
36 | this.watcher = compiler.watch({}, async (err, stats) => {
37 | if (err) {
38 | console.log(err)
39 | return
40 | }
41 |
42 | log(`Webpack built Electron main process`)
43 | log()
44 | process.stdout.write(stats.toString({
45 | colors: true,
46 | modules: false,
47 | children: false,
48 | chunks: false,
49 | chunkModules: false
50 | }) + '\n')
51 | log()
52 |
53 | if (stats.hasErrors()) {
54 | warn(`Electron main build failed with errors`)
55 | return
56 | }
57 |
58 | await this.__stopElectron()
59 | this.__startElectron()
60 |
61 | resolve()
62 | })
63 | })
64 | }
65 |
66 | build (quasarConfig) {
67 | const cfg = quasarConfig.getBuildConfig()
68 |
69 | return new Promise((resolve, reject) => {
70 | spawn(
71 | nodePackager,
72 | [ 'install', '--production' ],
73 | cfg.build.distDir,
74 | code => {
75 | if (code) {
76 | warn(`⚠️ [FAIL] ${nodePackager} failed installing dependencies`)
77 | process.exit(1)
78 | }
79 | resolve()
80 | }
81 | )
82 | }).then(() => {
83 | return new Promise(async (resolve, reject) => {
84 | if (typeof cfg.electron.beforePackaging === 'function') {
85 | log('Running beforePackaging()')
86 | log()
87 |
88 | const result = cfg.electron.beforePackaging({
89 | appPaths,
90 | unpackagedDir: cfg.build.distDir
91 | })
92 |
93 | if (result && result.then) {
94 | await result
95 | }
96 |
97 | log()
98 | log('[SUCCESS] Done running beforePackaging()')
99 | }
100 | resolve()
101 | })
102 | }).then(() => {
103 | const
104 | bundlerName = cfg.electron.bundler,
105 | bundlerConfig = cfg.electron[bundlerName],
106 | bundler = require('./bundler').getBundler(bundlerName),
107 | pkgName = `electron-${bundlerName}`
108 |
109 | return new Promise((resolve, reject) => {
110 | log(`Bundling app with electron-${bundlerName}...`)
111 | log()
112 |
113 | const bundlePromise = bundlerName === 'packager'
114 | ? bundler(bundlerConfig)
115 | : bundler.build(bundlerConfig)
116 |
117 | bundlePromise
118 | .then(() => {
119 | log()
120 | log(`[SUCCESS] ${pkgName} built the app`)
121 | log()
122 | resolve()
123 | })
124 | .catch(err => {
125 | log()
126 | warn(`⚠️ [FAIL] ${pkgName} could not build`)
127 | log()
128 | console.error(err + '\n')
129 | reject()
130 | })
131 | })
132 | })
133 | }
134 |
135 | stop () {
136 | return new Promise((resolve, reject) => {
137 | const finalize = () => {
138 | this.__stopElectron().then(resolve)
139 | }
140 |
141 | if (this.watcher) {
142 | this.watcher.close(finalize)
143 | this.watcher = null
144 | return
145 | }
146 |
147 | finalize()
148 | })
149 | }
150 |
151 | __startElectron () {
152 | log(`Booting up Electron process...`)
153 | this.pid = spawn(
154 | require(appPaths.resolve.app('node_modules/electron')),
155 | [
156 | '--inspect=5858',
157 | appPaths.resolve.app('.quasar/electron/electron-main.js')
158 | ],
159 | appPaths.appDir,
160 | code => {
161 | if (code) {
162 | warn()
163 | warn(`⚠️ Electron process ended with error code: ${code}`)
164 | warn()
165 | process.exit(1)
166 | }
167 |
168 | if (this.killPromise) {
169 | this.killPromise()
170 | this.killPromise = null
171 | }
172 | else { // else it wasn't killed by us
173 | warn()
174 | warn('Electron process was killed. Exiting...')
175 | warn()
176 | process.exit(0)
177 | }
178 | }
179 | )
180 | }
181 |
182 | __stopElectron () {
183 | const pid = this.pid
184 |
185 | if (!pid) {
186 | return Promise.resolve()
187 | }
188 |
189 | log('Shutting down Electron process...')
190 | this.pid = 0
191 | return new Promise((resolve, reject) => {
192 | this.killPromise = resolve
193 | process.kill(pid)
194 | })
195 | }
196 | }
197 |
198 | module.exports = new ElectronRunner()
199 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/helpers/banner.js:
--------------------------------------------------------------------------------
1 | const { green, grey, underline } = require('chalk')
2 | const
3 | appPaths = require('../app-paths'),
4 | {
5 | version,
6 | dependencies: { 'quasar-framework': quasarVersion }
7 | } = require(appPaths.resolve.cli('package.json'))
8 |
9 | module.exports = function (argv, cmd, details) {
10 | let banner = ''
11 |
12 | if (details) {
13 | banner += `
14 | ${underline('Build succeeded')}
15 | `
16 | }
17 |
18 | banner += `
19 | ${cmd === 'dev' ? 'Dev mode..........' : 'Build mode........'} ${green(argv.mode)}
20 | Quasar theme...... ${green(argv.theme)}
21 | Quasar CLI........ ${green('v' + version)}
22 | Quasar Framework.. ${green('v' + quasarVersion)}
23 | Debugging......... ${cmd === 'dev' || argv.debug ? green('enabled') : grey('no')}`
24 |
25 | if (details) {
26 | banner += `
27 | ==================
28 | Output folder..... ${green(details.outputFolder)}`
29 |
30 | if (argv.mode === 'ssr') {
31 | banner += `
32 |
33 | Tip: The output folder must be yarn/npm installed before using it,
34 | except when it is run inside your already yarn/npm installed project folder.
35 |
36 | Tip: Notice the package.json generated, where there's a script defined:
37 | "start": "node index.js"
38 | Running "$ yarn start" or "$ npm run start" from the output folder will
39 | start the webserver. Alternatively you can call "$ node index.js"
40 | yourself.`
41 | }
42 | else if (argv.mode === 'cordova') {
43 | banner += `
44 |
45 | Tip: "src-cordova" is a Cordova project folder, so everything you know
46 | about Cordova applies to it. Quasar CLI only generates the content
47 | for "src-cordova/www" folder and then Cordova takes over and builds
48 | the mobile app.
49 |
50 | Tip: Feel free to use Cordova CLI or change any files in "src-cordova",
51 | except for "www" folder which must be built by Quasar CLI.`
52 | }
53 | else if (['spa', 'pwa'].includes(argv.mode)) {
54 | banner += `
55 |
56 | Tip: Built files are meant to be served over an HTTP server
57 | Opening index.html over file:// won't work
58 |
59 | Tip: You can use "$ quasar serve" command to create a web server,
60 | both for testing or production. Type "$ quasar serve -h" for
61 | parameters. Also, an npm script (usually named "start") can
62 | be added for deployment environments.
63 | If you're using Vue Router "history" mode, don't forget to
64 | specify the "--history" parameter: "$ quasar serve --history"`
65 | }
66 | }
67 |
68 | console.log(banner + '\n')
69 | }
70 |
71 | module.exports.devCompilationSuccess = function (ctx, url) {
72 | return `App URL........... ${green(url)}
73 | Dev mode.......... ${green(ctx.modeName + (ctx.mode.ssr && ctx.mode.pwa ? ' + pwa' : ''))}
74 | Quasar theme...... ${green(ctx.themeName)}
75 | Quasar CLI........ ${green('v' + version)}
76 | Quasar Framework.. ${green('v' + quasarVersion)}
77 | `
78 | }
79 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/lib/legacy-validations.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | fse = require('fs-extra'),
4 | { green } = require('chalk')
5 |
6 | const
7 | appPaths = require('./app-paths')
8 |
9 | module.exports = function legacyValidations (cfg) {
10 | let file, content, error = false
11 |
12 | file = appPaths.resolve.app(cfg.sourceFiles.indexHtmlTemplate)
13 | if (!fs.existsSync(file)) {
14 | console.log('⚠️ Missing /src/index.template.html file...')
15 | console.log()
16 | error = true
17 | }
18 | content = fs.readFileSync(file, 'utf-8')
19 | if (content.indexOf(' -1) {
20 | console.log(`⚠️ Your newer Quasar CLI requires a minor change to /src/index.template.html
21 | Please remove this tag completely:
22 |
23 | `)
24 | console.log()
25 | error = true
26 | }
27 |
28 | if (content.indexOf(`chunk.initial ? 'preload' : 'prefetch'`) > -1) {
29 | console.log(`\n⚠️ Your newer Quasar CLI requires a minor change to /src/index.template.html
30 | Please remove this section completely:
31 |
32 |
36 | <% if (!['cordova', 'electron'].includes(htmlWebpackPlugin.options.ctx.modeName) && htmlWebpackPlugin.options.ctx.prod) {
37 | for (var chunk of webpack.chunks) {
38 | for (var file of chunk.files) {
39 | if (file.match(/\.(js|css)$/)) { %>
40 |
41 | <% }}}} %>
42 | `)
43 | console.log()
44 | error = true
45 | }
46 |
47 | if (content.indexOf(' -1) {
48 | console.log(`\n⚠️ Your newer Quasar CLI requires a minor change to /src/index.template.html
49 | Please remove this section completely:
50 |
51 | <% if (htmlWebpackPlugin.options.ctx.mode.pwa) { %>
52 |
53 | .....
54 | <% } %>
55 | `)
56 | console.log()
57 | error = true
58 | }
59 |
60 | if (content.indexOf('htmlWebpackPlugin.options.headScripts') > -1) {
61 | console.log(`\n⚠️ Your newer Quasar CLI requires a minor change to /src/index.template.html
62 | Please remove this section completely:
63 |
64 | <%= htmlWebpackPlugin.options.headScripts %>
65 | `)
66 | console.log()
67 | error = true
68 | }
69 |
70 | if (content.indexOf('htmlWebpackPlugin.options.bodyScripts') > -1) {
71 | console.log(`\n⚠️ Your newer Quasar CLI requires a minor change to /src/index.template.html
72 | Please remove this section completely:
73 |
74 | <%= htmlWebpackPlugin.options.bodyScripts %>
75 | `)
76 | console.log()
77 | error = true
78 | }
79 |
80 | file = appPaths.resolve.app(cfg.sourceFiles.rootComponent)
81 | content = fs.readFileSync(file, 'utf-8')
82 | if (content.indexOf('q-app') === -1) {
83 | console.log(`\n⚠️ Quasar CLI requires a minor change to the root component:
84 | ${file}
85 |
86 | Please add: id="q-app" (or write #q-app if using Pug)
87 | to the outermost HTML element of the template.
88 |
89 | ${green('Example:')}
90 |
91 |
92 | ...
93 |
94 |
95 | `)
96 | error = true
97 | }
98 |
99 | if (error) {
100 | process.exit(1)
101 | }
102 |
103 | file = appPaths.resolve.app(cfg.sourceFiles.router)
104 | content = fs.readFileSync(file, 'utf-8')
105 | if (cfg.ctx.mode.ssr && content.indexOf('export default function') === -1) {
106 | console.log(`\n⚠️ In order to build with SSR mode you need a minor change to the ROUTER file
107 | This won't break other build modes after you change it.
108 |
109 | ${file}
110 |
111 | You need to have a default export set to "function ({ store })" which returns a new
112 | instance of Router instead of default exporting the Router instance itself.
113 |
114 | ${green('OLD WAY:')}
115 | import Vue from 'vue'
116 | import VueRouter from 'vue-router'
117 | import routes from './routes'
118 | Vue.use(VueRouter)
119 |
120 | // in the new way, we'll wrap the instantiation into:
121 | // export default function ({ store }) --> store is optional
122 | const Router = new VueRouter({
123 | scrollBehavior: () => ({ y: 0 }),
124 | routes,
125 | // Leave these as they are and change from quasar.conf.js instead!
126 | mode: process.env.VUE_ROUTER_MODE,
127 | base: process.env.VUE_ROUTER_BASE,
128 | })
129 |
130 | // in the new way, this will be no more
131 | export default Router
132 |
133 | ${green('NEW WAY:')}
134 | import Vue from 'vue'
135 | import VueRouter from 'vue-router'
136 | import routes from './routes'
137 | Vue.use(VueRouter)
138 |
139 | // DO NOT import the store here as you will receive it as
140 | // parameter in the default exported function:
141 |
142 | export default function (/* { store } */) {
143 | // IMPORTANT! Instantiate Router inside this function
144 |
145 | const Router = new VueRouter({
146 | scrollBehavior: () => ({ y: 0 }),
147 | routes,
148 | // Leave these as they are and change from quasar.conf.js instead!
149 | mode: process.env.VUE_ROUTER_MODE,
150 | base: process.env.VUE_ROUTER_BASE,
151 | })
152 |
153 | return Router
154 | }
155 | `)
156 | console.log()
157 | process.exit(1)
158 | }
159 |
160 | if (cfg.store && cfg.ctx.mode.ssr) {
161 | file = appPaths.resolve.app(cfg.sourceFiles.store)
162 | content = fs.readFileSync(file, 'utf-8')
163 | if (content.indexOf('export default function') === -1) {
164 | console.log(`\n⚠️ In order to build with SSR mode you need a minor change to the STORE file
165 | This won't break other build modes after you change it.
166 |
167 | ${file}
168 |
169 | You need to have a default export set to "function ()" which returns a new
170 | instance of Vuex Store instead of default exporting the Store instance itself.
171 |
172 | ${green('OLD WAY:')}
173 | import Vue from 'vue'
174 | import Vuex from 'vuex'
175 | import example from './module-example'
176 | Vue.use(Vuex)
177 |
178 | // in the new way, we'll wrap the instantiation into:
179 | // export default function ()
180 | const store = new Vuex.Store({
181 | modules: {
182 | example
183 | }
184 | })
185 |
186 | // in the new way, this will be no more
187 | export default store
188 |
189 | ${green('NEW WAY:')}
190 | import Vue from 'vue'
191 | import Vuex from 'vuex'
192 | import example from './module-example'
193 | Vue.use(Vuex)
194 |
195 | export default function () {
196 | // IMPORTANT! Instantiate Store inside this function
197 |
198 | const Store = new Vuex.Store({
199 | modules: {
200 | example
201 | }
202 | })
203 |
204 | return Store
205 | }
206 | `)
207 | console.log()
208 | process.exit(1)
209 | }
210 | }
211 |
212 | file = appPaths.resolve.app('.babelrc')
213 | if (!fs.existsSync(file)) {
214 | console.log('⚠️ Missing .babelrc file...')
215 | console.log()
216 | process.exit(1)
217 | }
218 | content = fs.readFileSync(file, 'utf-8')
219 | if (content.indexOf('"transform-runtime"') > -1) {
220 | console.log()
221 | console.log(' ⚠️ WARNING')
222 | console.log(` Your newer Quasar CLI requires a change to .babelrc file.`)
223 | console.log(` Doing it automatically. Please review the changes.`)
224 | console.log()
225 |
226 | fse.copySync(
227 | appPaths.resolve.cli('templates/app/babelrc'),
228 | appPaths.resolve.app('.babelrc')
229 | )
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/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/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/mode/mode-cordova.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-cordova'),
7 | warn = logger('app:mode-cordova', 'red'),
8 | spawn = require('../helpers/spawn')
9 |
10 | function ensureNpmInstalled () {
11 | if (fs.existsSync(appPaths.resolve.cordova('node_modules'))) {
12 | return
13 | }
14 |
15 | log('Installing dependencies in /src-cordova')
16 | spawn.sync(
17 | 'npm',
18 | [ 'install' ],
19 | appPaths.cordovaDir,
20 | () => {
21 | warn(`⚠️ [FAIL] npm failed installing dependencies in /src-cordova`)
22 | process.exit(1)
23 | }
24 | )
25 | }
26 |
27 | class Mode {
28 | get isInstalled () {
29 | return fs.existsSync(appPaths.cordovaDir)
30 | }
31 |
32 | add (target) {
33 | if (this.isInstalled) {
34 | warn(`Cordova support detected already. Aborting.`)
35 | return
36 | }
37 |
38 | const
39 | pkg = require(appPaths.resolve.app('package.json')),
40 | appName = pkg.productName || pkg.name || 'Quasar App'
41 |
42 | log('Creating Cordova source folder...')
43 |
44 | spawn.sync(
45 | 'cordova',
46 | ['create', 'src-cordova', pkg.cordovaId || 'org.quasar.cordova.app', appName],
47 | appPaths.appDir,
48 | () => {
49 | warn(`⚠️ There was an error trying to install Cordova support`)
50 | process.exit(1)
51 | }
52 | )
53 |
54 | log(`Cordova support was installed`)
55 | log(`App name was taken from package.json: "${appName}"`)
56 | log()
57 | warn(`If you want a different App name then remove Cordova support, edit productName field from package.json then add Cordova support again.`)
58 | warn()
59 |
60 | if (!target) {
61 | log(`Please manually add Cordova platforms using Cordova CLI from the newly created "src-cordova" folder.`)
62 | log()
63 | return
64 | }
65 |
66 | this.addPlatform(target)
67 | }
68 |
69 | hasPlatform (target) {
70 | return fs.existsSync(appPaths.resolve.cordova(`platforms/${target}`))
71 | }
72 |
73 | addPlatform (target) {
74 | fse.ensureDir(appPaths.resolve.cordova(`www`))
75 |
76 | if (this.hasPlatform(target)) {
77 | ensureNpmInstalled()
78 | return
79 | }
80 |
81 | log(`Adding Cordova platform "${target}"`)
82 | spawn.sync(
83 | 'cordova',
84 | ['platform', 'add', target],
85 | appPaths.cordovaDir,
86 | () => {
87 | warn(`⚠️ There was an error trying to install Cordova platform "${target}"`)
88 | process.exit(1)
89 | }
90 | )
91 | }
92 |
93 | remove () {
94 | if (!this.isInstalled) {
95 | warn(`No Cordova support detected. Aborting.`)
96 | return
97 | }
98 |
99 | fse.removeSync(appPaths.cordovaDir)
100 | log(`Cordova support was removed`)
101 | }
102 | }
103 |
104 | module.exports = Mode
105 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/quasar-config.js:
--------------------------------------------------------------------------------
1 | const
2 | path = require('path'),
3 | fs = require('fs'),
4 | merge = require('webpack-merge'),
5 | chokidar = require('chokidar'),
6 | debounce = require('lodash.debounce')
7 |
8 | const
9 | appPaths = require('./app-paths'),
10 | logger = require('./helpers/logger'),
11 | log = logger('app:quasar-conf'),
12 | warn = logger('app:quasar-conf', 'red'),
13 | legacyValidations = require('./legacy-validations')
14 |
15 | function getQuasarConfigCtx (opts) {
16 | const ctx = {
17 | dev: opts.dev || false,
18 | prod: opts.prod || false,
19 | theme: {},
20 | themeName: opts.theme,
21 | mode: {},
22 | modeName: opts.mode,
23 | target: {},
24 | targetName: opts.target,
25 | emulator: opts.emulator,
26 | arch: {},
27 | archName: opts.arch,
28 | bundler: {},
29 | bundlerName: opts.bundler,
30 | debug: opts.debug
31 | }
32 | ctx.theme[opts.theme] = true
33 | ctx.mode[opts.mode] = true
34 |
35 | if (opts.target) {
36 | ctx.target[opts.target] = true
37 | }
38 | if (opts.arch) {
39 | ctx.arch[opts.arch] = true
40 | }
41 | if (opts.bundler) {
42 | ctx.bundler[opts.bundler] = true
43 | }
44 |
45 | return ctx
46 | }
47 |
48 | function encode (obj) {
49 | return JSON.stringify(obj, (key, value) => {
50 | return typeof value === 'function'
51 | ? `/fn(${value.toString()})`
52 | : value
53 | })
54 | }
55 |
56 | function formatPublicPath (path) {
57 | if (!path) {
58 | return path
59 | }
60 | if (!path.startsWith('/')) {
61 | path = `/${path}`
62 | }
63 | if (!path.endsWith('/')) {
64 | path = `${path}/`
65 | }
66 | return path
67 | }
68 |
69 | function parseBuildEnv (env) {
70 | const obj = {}
71 | Object.keys(env).forEach(key => {
72 | try {
73 | obj[key] = JSON.parse(env[key])
74 | }
75 | catch (e) {
76 | obj[key] = ''
77 | }
78 | })
79 | return obj
80 | }
81 |
82 | /*
83 | * this.buildConfig - Compiled Object from quasar.conf.js
84 | * this.webpackConfig - Webpack config object for main thread
85 | * this.electronWebpackConfig - Webpack config object for electron main thread
86 | */
87 |
88 | class QuasarConfig {
89 | constructor (opts) {
90 | this.filename = appPaths.resolve.app('quasar.conf.js')
91 | this.pkg = require(appPaths.resolve.app('package.json'))
92 | this.opts = opts
93 | this.ctx = getQuasarConfigCtx(opts)
94 | this.watch = opts.onBuildChange || opts.onAppChange
95 |
96 | if (this.watch) {
97 | // Start watching for quasar.config.js changes
98 | chokidar
99 | .watch(this.filename, { watchers: { chokidar: { ignoreInitial: true } } })
100 | .on('change', debounce(async () => {
101 | console.log()
102 | log(`quasar.conf.js changed`)
103 |
104 | try {
105 | await this.prepare()
106 | }
107 | catch (e) {
108 | if (e.message !== 'NETWORK_ERROR') {
109 | console.log(e)
110 | warn(`quasar.conf.js has JS errors. Please fix them then save file again.`)
111 | warn()
112 | }
113 |
114 | return
115 | }
116 |
117 | this.compile()
118 |
119 | if (this.webpackConfigChanged) {
120 | opts.onBuildChange()
121 | }
122 | else {
123 | opts.onAppChange()
124 | }
125 | }, 1000))
126 |
127 | if (this.ctx.mode.ssr) {
128 | this.ssrExtensionFile = appPaths.resolve.ssr('extension.js')
129 |
130 | chokidar
131 | .watch(this.ssrExtensionFile, { watchers: { chokidar: { ignoreInitial: true } } })
132 | .on('change', debounce(async () => {
133 | console.log()
134 | log(`src-ssr/extension.js changed`)
135 |
136 | try {
137 | this.readSSRextension()
138 | }
139 | catch (e) {
140 | if (e.message !== 'NETWORK_ERROR') {
141 | console.log(e)
142 | warn(`src-ssr/extension.js has JS errors. Please fix them then save file again.`)
143 | warn()
144 | }
145 |
146 | return
147 | }
148 |
149 | opts.onBuildChange()
150 | }, 1000))
151 | }
152 | }
153 | }
154 |
155 | // synchronous for build
156 | async prepare () {
157 | this.readConfig()
158 |
159 | if (this.watch && this.ctx.mode.ssr) {
160 | this.readSSRextension()
161 | }
162 |
163 | const cfg = merge({
164 | ctx: this.ctx,
165 | css: false,
166 | plugins: false,
167 | animations: false,
168 | extras: false
169 | }, this.quasarConfigFunction(this.ctx))
170 |
171 | if (cfg.framework === void 0 || cfg.framework === 'all') {
172 | cfg.framework = {
173 | all: true
174 | }
175 | }
176 | cfg.framework.config = cfg.framework.config || {}
177 |
178 | if (this.ctx.dev) {
179 | cfg.devServer = cfg.devServer || {}
180 |
181 | if (this.opts.host) {
182 | cfg.devServer.host = this.opts.host
183 | }
184 | else if (!cfg.devServer.host) {
185 | cfg.devServer.host = '0.0.0.0'
186 | }
187 |
188 | if (this.opts.port) {
189 | cfg.devServer.port = this.opts.port
190 | }
191 | else if (!cfg.devServer.port) {
192 | cfg.devServer.port = 8080
193 | }
194 |
195 | if (
196 | this.address &&
197 | this.address.from.host === cfg.devServer.host &&
198 | this.address.from.port === cfg.devServer.port
199 | ) {
200 | cfg.devServer.host = this.address.to.host
201 | cfg.devServer.port = this.address.to.port
202 | }
203 | else {
204 | const addr = {
205 | host: cfg.devServer.host,
206 | port: cfg.devServer.port
207 | }
208 | const to = await this.opts.onAddress(addr)
209 |
210 | // if network error while running
211 | if (to === null) {
212 | throw new Error('NETWORK_ERROR')
213 | }
214 |
215 | cfg.devServer = merge(cfg.devServer, to)
216 | this.address = {
217 | from: addr,
218 | to: {
219 | host: cfg.devServer.host,
220 | port: cfg.devServer.port
221 | }
222 | }
223 | }
224 | }
225 |
226 | this.quasarConfig = cfg
227 | }
228 |
229 | getBuildConfig () {
230 | return this.buildConfig
231 | }
232 |
233 | getWebpackConfig () {
234 | return this.webpackConfig
235 | }
236 |
237 | readConfig () {
238 | log(`Reading quasar.conf.js`)
239 |
240 | if (fs.existsSync(this.filename)) {
241 | delete require.cache[this.filename]
242 | this.quasarConfigFunction = require(this.filename)
243 | }
244 | else {
245 | warn(`⚠️ [FAIL] Could not load quasar.conf.js config file`)
246 | process.exit(1)
247 | }
248 | }
249 |
250 | readSSRextension () {
251 | log(`Reading src-ssr/extension.js`)
252 |
253 | if (fs.existsSync(this.ssrExtensionFile)) {
254 | delete require.cache[this.ssrExtensionFile]
255 | this.ssrExtension = require(this.ssrExtensionFile)
256 | }
257 | else {
258 | warn(`⚠️ [FAIL] Could not load src-ssr/extension.js file`)
259 | process.exit(1)
260 | }
261 | }
262 |
263 | compile () {
264 | let cfg = this.quasarConfig
265 |
266 | // if watching for changes,
267 | // then determine the type (webpack related or not)
268 | if (this.watch) {
269 | const newConfigSnapshot = [
270 | cfg.build ? encode(cfg.build) : '',
271 | cfg.ssr ? cfg.ssr.pwa : '',
272 | cfg.framework.all,
273 | cfg.devServer ? encode(cfg.devServer) : '',
274 | cfg.pwa ? encode(cfg.pwa) : '',
275 | cfg.electron ? encode(cfg.electron) : ''
276 | ].join('')
277 |
278 | if (this.oldConfigSnapshot) {
279 | this.webpackConfigChanged = newConfigSnapshot !== this.oldConfigSnapshot
280 | }
281 |
282 | this.oldConfigSnapshot = newConfigSnapshot
283 | }
284 |
285 | // make sure it exists
286 | cfg.supportIE = this.ctx.mode.electron
287 | ? false
288 | : (cfg.supportIE || false)
289 |
290 | cfg.vendor = merge({
291 | vendor: {
292 | add: false,
293 | remove: false
294 | }
295 | }, cfg.vendor || {})
296 |
297 | if (cfg.vendor.add) {
298 | cfg.vendor.add = cfg.vendor.add.filter(v => v).join('|')
299 | if (cfg.vendor.add) {
300 | cfg.vendor.add = new RegExp(cfg.vendor.add)
301 | }
302 | }
303 | if (cfg.vendor.remove) {
304 | cfg.vendor.remove = cfg.vendor.remove.filter(v => v).join('|')
305 | if (cfg.vendor.remove) {
306 | cfg.vendor.remove = new RegExp(cfg.vendor.remove)
307 | }
308 | }
309 |
310 | if (cfg.css) {
311 | cfg.css = cfg.css.filter(_ => _).map(
312 | asset => asset[0] === '~' ? asset.substring(1) : `src/css/${asset}`
313 | )
314 |
315 | if (cfg.css.length === 0) {
316 | cfg.css = false
317 | }
318 | }
319 |
320 | if (cfg.plugins) {
321 | cfg.plugins = cfg.plugins.filter(_ => _).map(asset => {
322 | return typeof asset === 'string'
323 | ? { path: asset }
324 | : asset
325 | }).filter(asset => asset.path)
326 |
327 | if (cfg.plugins.length === 0) {
328 | cfg.plugins = false
329 | }
330 | }
331 |
332 | cfg.build = merge({
333 | showProgress: true,
334 | scopeHoisting: true,
335 | productName: this.pkg.productName,
336 | productDescription: this.pkg.description,
337 | extractCSS: this.ctx.prod,
338 | sourceMap: this.ctx.dev,
339 | minify: this.ctx.prod,
340 | distDir: path.join('dist', `${this.ctx.modeName}-${this.ctx.themeName}`),
341 | htmlFilename: 'index.html',
342 | webpackManifest: this.ctx.prod,
343 | vueRouterMode: 'hash',
344 | preloadChunks: true,
345 | transpileDependencies: [],
346 | devtool: this.ctx.dev
347 | ? '#cheap-module-eval-source-map'
348 | : '#source-map',
349 | env: {
350 | NODE_ENV: `"${this.ctx.prod ? 'production' : 'development'}"`,
351 | CLIENT: true,
352 | SERVER: false,
353 | DEV: this.ctx.dev,
354 | PROD: this.ctx.prod,
355 | THEME: `"${this.ctx.themeName}"`,
356 | MODE: `"${this.ctx.modeName}"`
357 | },
358 | uglifyOptions: {
359 | compress: {
360 | // turn off flags with small gains to speed up minification
361 | arrows: false,
362 | collapse_vars: false, // 0.3kb
363 | comparisons: false,
364 | computed_props: false,
365 | hoist_funs: false,
366 | hoist_props: false,
367 | hoist_vars: false,
368 | inline: false,
369 | loops: false,
370 | negate_iife: false,
371 | properties: false,
372 | reduce_funcs: false,
373 | reduce_vars: false,
374 | switches: false,
375 | toplevel: false,
376 | typeofs: false,
377 |
378 | // a few flags with noticable gains/speed ratio
379 | // numbers based on out of the box vendor bundle
380 | booleans: true, // 0.7kb
381 | if_return: true, // 0.4kb
382 | sequences: true, // 0.7kb
383 | unused: true, // 2.3kb
384 |
385 | // required features to drop conditional branches
386 | conditionals: true,
387 | dead_code: true,
388 | evaluate: true
389 | },
390 | mangle: {
391 | /*
392 | Support non-standard Safari 10/11.
393 | By default `uglify-es` will not work around
394 | Safari 10/11 bugs.
395 | */
396 | safari10: true
397 | }
398 | }
399 | }, cfg.build || {})
400 |
401 | cfg.build.transpileDependencies.push(/[\\/]node_modules[\\/]quasar-framework[\\/]/)
402 |
403 | cfg.__loadingBar = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('LoadingBar'))
404 | cfg.__meta = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('Meta'))
405 |
406 | if (this.ctx.dev || this.ctx.debug) {
407 | Object.assign(cfg.build, {
408 | minify: false,
409 | extractCSS: false,
410 | gzip: false
411 | })
412 | }
413 | if (this.ctx.debug) {
414 | cfg.build.sourceMap = true
415 | }
416 |
417 | if (this.ctx.mode.ssr) {
418 | Object.assign(cfg.build, {
419 | extractCSS: false,
420 | vueRouterMode: 'history',
421 | publicPath: '/',
422 | gzip: false
423 | })
424 | }
425 | else if (this.ctx.mode.cordova || this.ctx.mode.electron) {
426 | Object.assign(cfg.build, {
427 | extractCSS: false,
428 | htmlFilename: 'index.html',
429 | vueRouterMode: 'hash',
430 | gzip: false,
431 | webpackManifest: false
432 | })
433 | }
434 |
435 | if (this.ctx.mode.cordova) {
436 | cfg.build.distDir = appPaths.resolve.app(path.join('src-cordova', 'www'))
437 | }
438 | else if (!path.isAbsolute(cfg.build.distDir)) {
439 | cfg.build.distDir = appPaths.resolve.app(cfg.build.distDir)
440 | }
441 |
442 | if (this.ctx.mode.electron) {
443 | cfg.build.packagedElectronDist = cfg.build.distDir
444 | cfg.build.distDir = path.join(cfg.build.distDir, 'UnPackaged')
445 | }
446 |
447 | cfg.build.publicPath =
448 | this.ctx.prod && cfg.build.publicPath && ['spa', 'pwa'].includes(this.ctx.modeName)
449 | ? formatPublicPath(cfg.build.publicPath)
450 | : (cfg.build.vueRouterMode !== 'hash' ? '/' : '')
451 | cfg.build.appBase = cfg.build.vueRouterMode === 'history'
452 | ? cfg.build.publicPath
453 | : ''
454 |
455 | cfg.sourceFiles = merge({
456 | rootComponent: 'src/App.vue',
457 | router: 'src/router/index.js',
458 | store: 'src/store/index.js',
459 | indexHtmlTemplate: 'src/index.template.html',
460 | registerServiceWorker: 'src-pwa/register-service-worker.js',
461 | serviceWorker: 'src-pwa/custom-service-worker.js',
462 | electronMainDev: 'src-electron/main-process/electron-main.dev.js',
463 | electronMainProd: 'src-electron/main-process/electron-main.js',
464 | ssrServerIndex: 'src-ssr/index.js'
465 | }, cfg.sourceFiles || {})
466 |
467 | // do we got vuex?
468 | cfg.store = fs.existsSync(appPaths.resolve.app(cfg.sourceFiles.store))
469 |
470 | //make sure we have preFetch in config
471 | cfg.preFetch = cfg.preFetch || false
472 |
473 | if (cfg.animations === 'all') {
474 | cfg.animations = require('./helpers/animations')
475 | }
476 |
477 | if (this.ctx.mode.ssr) {
478 | cfg.ssr = merge({
479 | pwa: false,
480 | componentCache: {
481 | max: 1000,
482 | maxAge: 1000 * 60 * 15
483 | }
484 | }, cfg.ssr || {})
485 |
486 | cfg.ssr.debug = this.ctx.debug
487 |
488 | cfg.ssr.__templateOpts = JSON.stringify(cfg.ssr, null, 2)
489 | cfg.ssr.__templateFlags = {
490 | meta: cfg.__meta
491 | }
492 |
493 | const file = appPaths.resolve.app(cfg.sourceFiles.ssrServerIndex)
494 | cfg.ssr.__dir = path.dirname(file)
495 | cfg.ssr.__index = path.basename(file)
496 |
497 | if (cfg.ssr.pwa) {
498 | require('./mode/install-missing')('pwa')
499 | }
500 | this.ctx.mode.pwa = cfg.ctx.mode.pwa = cfg.ssr.pwa !== false
501 |
502 | this.watch && (cfg.__ssrExtension = this.ssrExtension)
503 | }
504 |
505 | if (this.ctx.dev) {
506 | const
507 | initialPort = cfg.devServer && cfg.devServer.port,
508 | initialHost = cfg.devServer && cfg.devServer.host
509 |
510 | cfg.devServer = merge({
511 | publicPath: cfg.build.publicPath,
512 | hot: true,
513 | inline: true,
514 | overlay: true,
515 | quiet: true,
516 | historyApiFallback: !this.ctx.mode.ssr,
517 | noInfo: true,
518 | disableHostCheck: true,
519 | compress: true,
520 | open: true
521 | }, cfg.devServer || {}, {
522 | contentBase: [ appPaths.srcDir ]
523 | })
524 |
525 | if (this.ctx.mode.ssr) {
526 | cfg.devServer.contentBase = false
527 | }
528 | else if (this.ctx.mode.cordova || this.ctx.mode.electron) {
529 | cfg.devServer.open = false
530 |
531 | if (this.ctx.mode.electron) {
532 | cfg.devServer.https = false
533 | }
534 | }
535 |
536 | if (this.ctx.mode.cordova) {
537 | cfg.devServer.contentBase.push(
538 | appPaths.resolve.cordova(`platforms/${this.ctx.targetName}/platform_www`)
539 | )
540 | }
541 |
542 | if (cfg.devServer.open) {
543 | const isMinimalTerminal = require('./helpers/is-minimal-terminal')
544 | if (isMinimalTerminal) {
545 | cfg.devServer.open = false
546 | }
547 | }
548 | }
549 |
550 | if (cfg.build.gzip) {
551 | let gzip = cfg.build.gzip === true
552 | ? {}
553 | : cfg.build.gzip
554 | let ext = ['js', 'css']
555 |
556 | if (gzip.extensions) {
557 | ext = gzip.extensions
558 | delete gzip.extensions
559 | }
560 |
561 | cfg.build.gzip = merge({
562 | filename: '[path].gz[query]',
563 | algorithm: 'gzip',
564 | test: new RegExp('\\.(' + ext.join('|') + ')$'),
565 | threshold: 10240,
566 | minRatio: 0.8
567 | }, gzip)
568 | }
569 |
570 | if (this.ctx.mode.pwa) {
571 | cfg.build.webpackManifest = false
572 |
573 | cfg.pwa = merge({
574 | workboxPluginMode: 'GenerateSW',
575 | workboxOptions: {},
576 | manifest: {
577 | name: this.pkg.productName || this.pkg.name || 'Quasar App',
578 | short_name: this.pkg.name || 'quasar-pwa',
579 | description: this.pkg.description,
580 | display: 'standalone',
581 | start_url: '.'
582 | }
583 | }, cfg.pwa || {})
584 |
585 | if (!['GenerateSW', 'InjectManifest'].includes(cfg.pwa.workboxPluginMode)) {
586 | console.log()
587 | console.log(`⚠️ Workbox webpack plugin mode "${cfg.pwa.workboxPluginMode}" is invalid.`)
588 | console.log(` Valid Workbox modes are: GenerateSW, InjectManifest`)
589 | console.log()
590 | process.exit(1)
591 | }
592 | if (cfg.pwa.cacheExt) {
593 | console.log()
594 | console.log(`⚠️ Quasar CLI now uses Workbox, so quasar.conf.js > pwa > cacheExt is no longer relevant.`)
595 | console.log(` Please remove this property and try again.`)
596 | console.log()
597 | process.exit(1)
598 | }
599 | if (
600 | fs.existsSync(appPaths.resolve.pwa('service-worker-dev.js')) ||
601 | fs.existsSync(appPaths.resolve.pwa('service-worker-prod.js'))
602 | ) {
603 | console.log()
604 | console.log(`⚠️ Quasar CLI now uses Workbox, so src-pwa/service-worker-dev.js and src-pwa/service-worker-prod.js are obsolete.`)
605 | console.log(` Please remove and add PWA mode again:`)
606 | console.log(` $ quasar mode -r pwa # Warning: this will delete /src-pwa !`)
607 | console.log(` $ quasar mode -a pwa`)
608 | console.log()
609 | process.exit(1)
610 | }
611 |
612 | cfg.pwa.manifest.icons = cfg.pwa.manifest.icons.map(icon => {
613 | icon.src = `${cfg.build.publicPath}${icon.src}`
614 | return icon
615 | })
616 | }
617 |
618 | if (this.ctx.dev) {
619 | const host = cfg.devServer.host === '0.0.0.0'
620 | ? 'localhost'
621 | : cfg.devServer.host
622 | const urlPath = `${cfg.build.vueRouterMode === 'hash' ? (cfg.build.htmlFilename !== 'index.html' ? cfg.build.htmlFilename : '') : ''}`
623 | cfg.build.APP_URL = `http${cfg.devServer.https ? 's' : ''}://${host}:${cfg.devServer.port}/${urlPath}`
624 | }
625 | else if (this.ctx.mode.cordova) {
626 | cfg.build.APP_URL = 'index.html'
627 | }
628 | else if (this.ctx.mode.electron) {
629 | cfg.build.APP_URL = `file://" + __dirname + "/index.html`
630 | }
631 |
632 | cfg.build.env = merge(cfg.build.env || {}, {
633 | VUE_ROUTER_MODE: `"${cfg.build.vueRouterMode}"`,
634 | VUE_ROUTER_BASE: `"${cfg.build.publicPath}"`,
635 | APP_URL: `"${cfg.build.APP_URL}"`
636 | })
637 |
638 | if (this.ctx.mode.pwa) {
639 | cfg.build.env.SERVICE_WORKER_FILE = `"${cfg.build.publicPath}service-worker.js"`
640 | }
641 |
642 | cfg.build.env = {
643 | 'process.env': cfg.build.env
644 | }
645 |
646 | if (this.ctx.mode.electron) {
647 | if (this.ctx.dev) {
648 | cfg.build.env.__statics = `"${appPaths.resolve.src('statics').replace(/\\/g, '\\\\')}"`
649 | }
650 | }
651 | else {
652 | cfg.build.env.__statics = `"${this.ctx.dev ? '/' : cfg.build.publicPath || '/'}statics"`
653 | }
654 |
655 | legacyValidations(cfg)
656 |
657 | if (this.ctx.mode.cordova && !cfg.cordova) {
658 | cfg.cordova = {}
659 | }
660 |
661 | if (this.ctx.mode.electron) {
662 | if (this.ctx.prod) {
663 | const bundler = require('./electron/bundler')
664 |
665 | cfg.electron = merge({
666 | packager: {
667 | asar: true,
668 | icon: appPaths.resolve.electron('icons/icon'),
669 | overwrite: true
670 | },
671 | builder: {
672 | appId: 'quasar-app',
673 | productName: this.pkg.productName || this.pkg.name || 'Quasar App',
674 | directories: {
675 | buildResources: appPaths.resolve.electron('')
676 | }
677 | }
678 | }, cfg.electron || {}, {
679 | packager: {
680 | dir: cfg.build.distDir,
681 | out: cfg.build.packagedElectronDist
682 | },
683 | builder: {
684 | directories: {
685 | app: cfg.build.distDir,
686 | output: path.join(cfg.build.packagedElectronDist, 'Packaged')
687 | }
688 | }
689 | })
690 |
691 | if (cfg.ctx.bundlerName) {
692 | cfg.electron.bundler = cfg.ctx.bundlerName
693 | }
694 | else if (!cfg.electron.bundler) {
695 | cfg.electron.bundler = bundler.getDefaultName()
696 | }
697 |
698 | if (cfg.electron.bundler === 'packager') {
699 | if (cfg.ctx.targetName) {
700 | cfg.electron.packager.platform = cfg.ctx.targetName
701 | }
702 | if (cfg.ctx.archName) {
703 | cfg.electron.packager.arch = cfg.ctx.archName
704 | }
705 | }
706 | else {
707 | cfg.electron.builder = {
708 | platform: cfg.ctx.targetName,
709 | arch: cfg.ctx.archName,
710 | config: cfg.electron.builder
711 | }
712 |
713 | bundler.ensureBuilderCompatibility()
714 | }
715 |
716 | bundler.ensureInstall(cfg.electron.bundler)
717 | }
718 | }
719 |
720 | cfg.__html = {
721 | variables: Object.assign({
722 | ctx: cfg.ctx,
723 | process: {
724 | env: parseBuildEnv(cfg.build.env['process.env'])
725 | },
726 | productName: cfg.build.productName,
727 | productDescription: cfg.build.productDescription
728 | }, cfg.htmlVariables || {}),
729 | minifyOptions: cfg.build.minify
730 | ? {
731 | removeComments: true,
732 | collapseWhitespace: true,
733 | removeAttributeQuotes: true
734 | // more options:
735 | // https://github.com/kangax/html-minifier#options-quick-reference
736 | }
737 | : undefined
738 | }
739 |
740 | this.webpackConfig = require('./webpack')(cfg)
741 | this.buildConfig = cfg
742 | }
743 | }
744 |
745 | module.exports = QuasarConfig
746 |
--------------------------------------------------------------------------------
/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/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/webpack/create-chain.js:
--------------------------------------------------------------------------------
1 | const
2 | path = require('path'),
3 | webpack = require('webpack'),
4 | WebpackChain = require('webpack-chain'),
5 | VueLoaderPlugin = require('vue-loader/lib/plugin'),
6 | WebpackProgress = require('./plugin.progress')
7 |
8 | const
9 | appPaths = require('../app-paths'),
10 | injectStyleRules = require('./inject.style-rules')
11 |
12 | module.exports = function (cfg, configName) {
13 | const
14 | chain = new WebpackChain(),
15 | needsHash = !cfg.ctx.dev && !['electron', 'cordova'].includes(cfg.ctx.modeName),
16 | fileHash = needsHash ? '.[hash:8]' : '',
17 | chunkHash = needsHash ? '.[contenthash:8]' : '',
18 | resolveModules = [
19 | 'node_modules',
20 | appPaths.resolve.app('node_modules'),
21 | appPaths.resolve.cli('node_modules')
22 | ]
23 |
24 | chain.entry('app').add(appPaths.resolve.app('.quasar/client-entry.js'))
25 | chain.mode(cfg.ctx.dev ? 'development' : 'production')
26 | chain.devtool(cfg.build.sourceMap ? cfg.build.devtool : false)
27 |
28 | if (cfg.ctx.prod || cfg.ctx.mode.ssr) {
29 | chain.output
30 | .path(
31 | cfg.ctx.mode.ssr
32 | ? path.join(cfg.build.distDir, 'www')
33 | : cfg.build.distDir
34 | )
35 | .publicPath(cfg.build.publicPath)
36 | .filename(`js/[name]${fileHash}.js`)
37 | .chunkFilename(`js/[name]${chunkHash}.js`)
38 | }
39 |
40 | chain.resolve.symlinks(false)
41 |
42 | chain.resolve.extensions
43 | .merge([ `.${cfg.ctx.themeName}.js`, '.js', '.vue' ])
44 |
45 | chain.resolve.modules
46 | .merge(resolveModules)
47 |
48 | chain.resolve.alias
49 | .merge({
50 | quasar: cfg.framework.all !== true
51 | ? `quasar-framework`
52 | : appPaths.resolve.app(`node_modules/quasar-framework/dist/quasar.${cfg.ctx.themeName}.esm.js`),
53 | src: appPaths.srcDir,
54 | app: appPaths.appDir,
55 | components: appPaths.resolve.src(`components`),
56 | layouts: appPaths.resolve.src(`layouts`),
57 | pages: appPaths.resolve.src(`pages`),
58 | assets: appPaths.resolve.src(`assets`),
59 | plugins: appPaths.resolve.src(`plugins`),
60 | variables: appPaths.resolve.app(`.quasar/variables.styl`),
61 |
62 | // CLI using these ones:
63 | 'quasar-app-styl': appPaths.resolve.app(`.quasar/app.styl`),
64 | 'quasar-app-variables': appPaths.resolve.src(`css/themes/variables.${cfg.ctx.themeName}.styl`),
65 | 'quasar-styl': `quasar-framework/dist/quasar.${cfg.ctx.themeName}.styl`,
66 | 'quasar-addon-styl': cfg.framework.cssAddon
67 | ? `quasar-framework/src/css/flex-addon.styl`
68 | : appPaths.resolve.app(`.quasar/empty.styl`)
69 | })
70 |
71 | if (cfg.build.vueCompiler) {
72 | chain.resolve.alias.set('vue$', 'vue/dist/vue.esm.js')
73 | }
74 |
75 | chain.resolveLoader.modules
76 | .merge(resolveModules)
77 |
78 | chain.module.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
79 |
80 | chain.module.rule('vue')
81 | .test(/\.vue$/)
82 | .use('vue-loader')
83 | .loader('vue-loader')
84 | .options({
85 | productionMode: cfg.ctx.prod,
86 | compilerOptions: {
87 | preserveWhitespace: false
88 | },
89 | transformAssetUrls: {
90 | video: 'src',
91 | source: 'src',
92 | img: 'src',
93 | image: 'xlink:href'
94 | }
95 | })
96 |
97 | chain.module.rule('babel')
98 | .test(/\.jsx?$/)
99 | .exclude
100 | .add(filepath => {
101 | // always transpile js(x) in Vue files
102 | if (/\.vue\.jsx?$/.test(filepath)) {
103 | return false
104 | }
105 |
106 | if (cfg.build.transpileDependencies.some(dep => filepath.match(dep))) {
107 | return false
108 | }
109 |
110 | // Don't transpile anything else in node_modules
111 | return /[\\/]node_modules[\\/]/.test(filepath)
112 | })
113 | .end()
114 | .use('babel-loader')
115 | .loader('babel-loader')
116 | .options({
117 | extends: appPaths.resolve.app('.babelrc'),
118 | plugins: cfg.framework.all !== true ? [
119 | [
120 | 'transform-imports', {
121 | quasar: {
122 | transform: `quasar-framework/dist/babel-transforms/imports.${cfg.ctx.themeName}.js`,
123 | preventFullImport: true
124 | }
125 | }
126 | ]
127 | ] : []
128 | })
129 |
130 | chain.module.rule('images')
131 | .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
132 | .use('url-loader')
133 | .loader('url-loader')
134 | .options({
135 | limit: 10000,
136 | name: `img/[name]${fileHash}.[ext]`
137 | })
138 |
139 | chain.module.rule('fonts')
140 | .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/)
141 | .use('url-loader')
142 | .loader('url-loader')
143 | .options({
144 | limit: 10000,
145 | name: `fonts/[name]${fileHash}.[ext]`
146 | })
147 |
148 | chain.module.rule('media')
149 | .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
150 | .use('url-loader')
151 | .loader('url-loader')
152 | .options({
153 | limit: 10000,
154 | name: `media/[name]${fileHash}.[ext]`
155 | })
156 |
157 | injectStyleRules(chain, {
158 | rtl: cfg.build.rtl,
159 | sourceMap: cfg.build.sourceMap,
160 | extract: cfg.build.extractCSS,
161 | minify: cfg.build.minify
162 | ? !cfg.build.extractCSS
163 | : false
164 | })
165 |
166 | chain.plugin('vue-loader')
167 | .use(VueLoaderPlugin)
168 |
169 | chain.plugin('define')
170 | .use(webpack.DefinePlugin, [ cfg.build.env ])
171 |
172 | if (cfg.build.showProgress) {
173 | chain.plugin('progress')
174 | .use(WebpackProgress, [{ name: configName }])
175 | }
176 |
177 | chain.performance
178 | .hints(false)
179 | .maxAssetSize(500000)
180 |
181 | // DEVELOPMENT build
182 | if (cfg.ctx.dev) {
183 | const
184 | FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'),
185 | { devCompilationSuccess } = require('../helpers/banner')
186 |
187 | chain.optimization
188 | .noEmitOnErrors(true)
189 |
190 | chain.plugin('friendly-errors')
191 | .use(FriendlyErrorsPlugin, [{
192 | clearConsole: true,
193 | compilationSuccessInfo: ['spa', 'pwa', 'ssr'].includes(cfg.ctx.modeName)
194 | ? { notes: [ devCompilationSuccess(cfg.ctx, cfg.build.APP_URL) ] }
195 | : undefined
196 | }])
197 | }
198 | // PRODUCTION build
199 | else {
200 | // keep module.id stable when vendor modules does not change
201 | chain.plugin('hashed-module-ids')
202 | .use(webpack.HashedModuleIdsPlugin, [{
203 | hashDigest: 'hex'
204 | }])
205 |
206 | // keep chunk ids stable so async chunks have consistent hash
207 | const hash = require('hash-sum')
208 | chain
209 | .plugin('named-chunks')
210 | .use(webpack.NamedChunksPlugin, [
211 | chunk => chunk.name || hash(
212 | Array.from(chunk.modulesIterable, m => m.id).join('_')
213 | )
214 | ])
215 |
216 | if (configName !== 'Server') {
217 | const
218 | add = cfg.vendor.add,
219 | rem = cfg.vendor.remove,
220 | regex = /[\\/]node_modules[\\/]/
221 |
222 | chain.optimization
223 | .splitChunks({
224 | cacheGroups: {
225 | vendors: {
226 | name: 'vendor',
227 | chunks: 'initial',
228 | priority: -10,
229 | // a module is extracted into the vendor chunk if...
230 | test: add || rem
231 | ? module => {
232 | if (module.resource) {
233 | if (add && add.test(module.resource)) { return true }
234 | if (rem && rem.test(module.resource)) { return false }
235 | }
236 | return regex.test(module.resource)
237 | }
238 | : module => regex.test(module.resource)
239 | },
240 | common: {
241 | name: `chunk-common`,
242 | minChunks: 2,
243 | priority: -20,
244 | chunks: 'initial',
245 | reuseExistingChunk: true
246 | }
247 | }
248 | })
249 |
250 | // extract webpack runtime and module manifest to its own file in order to
251 | // prevent vendor hash from being updated whenever app bundle is updated
252 | if (cfg.build.webpackManifest) {
253 | chain.optimization.runtimeChunk('single')
254 | }
255 |
256 | // copy statics to dist folder
257 | const CopyWebpackPlugin = require('copy-webpack-plugin')
258 | chain.plugin('copy-webpack')
259 | .use(CopyWebpackPlugin, [
260 | [{
261 | from: appPaths.resolve.src('statics'),
262 | to: 'statics',
263 | ignore: ['.*']
264 | }]
265 | ])
266 | }
267 |
268 | // Scope hoisting ala Rollupjs
269 | if (cfg.build.scopeHoisting) {
270 | chain.optimization
271 | .concatenateModules(true)
272 | }
273 |
274 | if (cfg.ctx.debug) {
275 | // reset default webpack 4 minimizer
276 | chain.optimization.minimizer([])
277 | }
278 | else if (cfg.build.minify) {
279 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
280 |
281 | chain.optimization
282 | .minimizer([
283 | new UglifyJSPlugin({
284 | uglifyOptions: cfg.build.uglifyOptions,
285 | cache: true,
286 | parallel: true,
287 | sourceMap: cfg.build.sourceMap
288 | })
289 | ])
290 | }
291 |
292 | // configure CSS extraction & optimize
293 | if (cfg.build.extractCSS) {
294 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
295 |
296 | // extract css into its own file
297 | chain.plugin('mini-css-extract')
298 | .use(MiniCssExtractPlugin, [{
299 | filename: 'css/[name].[contenthash:8].css'
300 | }])
301 |
302 | // dedupe & minify CSS (only if extracted)
303 | if (cfg.build.minify) {
304 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
305 |
306 | const cssProcessorOptions = {
307 | parser: require('postcss-safe-parser'),
308 | autoprefixer: { disable: true },
309 | mergeLonghand: false
310 | }
311 | if (cfg.build.sourceMap) {
312 | cssProcessorOptions.map = { inline: false }
313 | }
314 |
315 | // We are using this plugin so that possible
316 | // duplicated CSS = require(different components) can be deduped.
317 | chain.plugin('optimize-css')
318 | .use(OptimizeCSSPlugin, [{
319 | canPrint: false,
320 | cssProcessor: require('cssnano'),
321 | cssProcessorOptions
322 | }])
323 | }
324 | }
325 |
326 | if (configName !== 'Server') {
327 | // also produce a gzipped version
328 | if (cfg.build.gzip) {
329 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
330 | chain.plugin('compress-webpack')
331 | .use(CompressionWebpackPlugin, [ cfg.build.gzip ])
332 | }
333 |
334 | if (cfg.build.analyze) {
335 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
336 | chain.plugin('bundle-analyzer')
337 | .use(BundleAnalyzerPlugin, [ Object.assign({}, cfg.build.analyze) ])
338 | }
339 | }
340 | }
341 |
342 | return chain
343 | }
344 |
--------------------------------------------------------------------------------
/lib/webpack/electron/main.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | chalk = require('chalk'),
5 | webpack = require('webpack'),
6 | WebpackChain = require('webpack-chain'),
7 | WebpackProgress = require('../plugin.progress')
8 |
9 | const
10 | appPaths = require('../../app-paths')
11 |
12 | module.exports = function (cfg, configName) {
13 | const
14 | { dependencies:appDeps = {} } = require(appPaths.resolve.cli('package.json')),
15 | { dependencies:cliDeps = {} } = require(appPaths.resolve.app('package.json'))
16 |
17 | const chain = new WebpackChain()
18 | const resolveModules = [
19 | appPaths.resolve.app('node_modules'),
20 | appPaths.resolve.cli('node_modules')
21 | ]
22 |
23 | chain.target('electron-main')
24 | chain.mode(cfg.ctx.dev ? 'development' : 'production')
25 | chain.node
26 | .merge({
27 | __dirname: cfg.ctx.dev,
28 | __filename: cfg.ctx.dev
29 | })
30 |
31 | chain.entry('electron-main')
32 | .add(appPaths.resolve.app(
33 | cfg.ctx.dev ? cfg.sourceFiles.electronMainDev : cfg.sourceFiles.electronMainProd
34 | ))
35 |
36 | chain.output
37 | .filename('electron-main.js')
38 | .libraryTarget('commonjs2')
39 | .path(
40 | cfg.ctx.dev
41 | ? appPaths.resolve.app('.quasar/electron')
42 | : cfg.build.distDir
43 | )
44 |
45 | chain.externals([
46 | ...Object.keys(cliDeps),
47 | ...Object.keys(appDeps)
48 | ])
49 |
50 | chain.module.rule('babel')
51 | .test(/\.js$/)
52 | .exclude
53 | .add(/node_modules/)
54 | .end()
55 | .use('babel-loader')
56 | .loader('babel-loader')
57 | .options({
58 | extends: appPaths.resolve.app('.babelrc')
59 | })
60 |
61 | chain.module.rule('node')
62 | .test(/\.node$/)
63 | .use('node-loader')
64 | .loader('node-loader')
65 |
66 | chain.resolve.modules
67 | .merge(resolveModules)
68 |
69 | chain.resolve.extensions
70 | .merge([ '.js', '.json', '.node' ])
71 |
72 | chain.resolveLoader.modules
73 | .merge(resolveModules)
74 |
75 | chain.optimization
76 | .noEmitOnErrors(true)
77 |
78 | chain.plugin('progress')
79 | .use(WebpackProgress, [{ name: configName }])
80 |
81 | chain.plugin('define')
82 | .use(webpack.DefinePlugin, [ cfg.build.env ])
83 |
84 | if (cfg.ctx.prod) {
85 | if (cfg.build.minify) {
86 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
87 |
88 | chain.optimization
89 | .minimizer([
90 | new UglifyJSPlugin({
91 | uglifyOptions: cfg.build.uglifyOptions,
92 | cache: true,
93 | parallel: true,
94 | sourceMap: cfg.build.sourceMap
95 | })
96 | ])
97 | }
98 |
99 | const ElectronPackageJson = require('./plugin.electron-package-json')
100 |
101 | // write package.json file
102 | chain.plugin('package-json')
103 | .use(ElectronPackageJson)
104 | }
105 |
106 | return chain
107 | }
108 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/webpack/index.js:
--------------------------------------------------------------------------------
1 | const
2 | createChain = require('./create-chain'),
3 | log = require('../helpers/logger')('app:webpack')
4 |
5 | function getWebpackConfig (chain, cfg, {
6 | name,
7 | hot,
8 | cfgExtendBase = cfg.build,
9 | invokeParams
10 | }) {
11 | if (typeof cfgExtendBase.chainWebpack === 'function') {
12 | log(`Chaining ${name ? name + ' ' : ''}Webpack config`)
13 | cfgExtendBase.chainWebpack(chain, invokeParams)
14 | }
15 |
16 | const webpackConfig = chain.toConfig()
17 |
18 | if (typeof cfgExtendBase.extendWebpack === 'function') {
19 | log(`Extending ${name ? name + ' ' : ''}Webpack config`)
20 | cfgExtendBase.extendWebpack(webpackConfig, invokeParams)
21 | }
22 |
23 | if (hot && cfg.ctx.dev && cfg.devServer.hot) {
24 | // tap entries for HMR
25 | require('webpack-dev-server').addDevServerEntrypoints(webpackConfig, cfg.devServer)
26 | }
27 |
28 | return webpackConfig
29 | }
30 |
31 | function getSPA (cfg) {
32 | const chain = createChain(cfg, 'SPA')
33 | require('./spa')(chain, cfg)
34 | return getWebpackConfig(chain, cfg, {
35 | name: 'SPA',
36 | hot: true,
37 | invokeParams: { isClient: true, isServer: false }
38 | })
39 | }
40 |
41 | function getPWA (cfg) {
42 | const chain = createChain(cfg, 'PWA')
43 | require('./spa')(chain, cfg) // extending a SPA
44 | require('./pwa')(chain, cfg)
45 | return getWebpackConfig(chain, cfg, {
46 | name: 'PWA',
47 | hot: true,
48 | invokeParams: { isClient: true, isServer: false }
49 | })
50 | }
51 |
52 | function getCordova (cfg) {
53 | const chain = createChain(cfg, 'Cordova')
54 | require('./cordova')(chain, cfg)
55 | return getWebpackConfig(chain, cfg, {
56 | name: 'Cordova',
57 | hot: true,
58 | invokeParams: { isClient: true, isServer: false }
59 | })
60 | }
61 |
62 | function getElectron (cfg) {
63 | const
64 | rendererChain = createChain(cfg, 'Renderer process'),
65 | mainChain = require('./electron/main')(cfg, 'Main process')
66 |
67 | require('./electron/renderer')(rendererChain, cfg)
68 |
69 | return {
70 | renderer: getWebpackConfig(rendererChain, cfg, {
71 | name: 'Renderer process',
72 | hot: true,
73 | invokeParams: { isClient: true, isServer: false }
74 | }),
75 | main: getWebpackConfig(mainChain, cfg, {
76 | name: 'Main process',
77 | cfgExtendBase: cfg.electron
78 | })
79 | }
80 | }
81 |
82 | function getSSR (cfg) {
83 | const
84 | client = createChain(cfg, 'Client'),
85 | server = createChain(cfg, 'Server')
86 |
87 | require('./ssr/client')(client, cfg)
88 | if (cfg.ctx.mode.pwa) {
89 | require('./pwa')(client, cfg) // extending a PWA
90 | }
91 |
92 | require('./ssr/server')(server, cfg)
93 |
94 | return {
95 | client: getWebpackConfig(client, cfg, {
96 | name: 'Client',
97 | hot: true,
98 | invokeParams: { isClient: true, isServer: false }
99 | }),
100 | server: getWebpackConfig(server, cfg, {
101 | name: 'Server',
102 | invokeParams: { isClient: false, isServer: true }
103 | })
104 | }
105 | }
106 |
107 | module.exports = function (cfg) {
108 | const mode = cfg.ctx.mode
109 |
110 | if (mode.ssr) {
111 | return getSSR(cfg)
112 | }
113 | else if (mode.electron) {
114 | return getElectron(cfg)
115 | }
116 | else if (mode.cordova) {
117 | return getCordova(cfg)
118 | }
119 | else if (mode.pwa) {
120 | return getPWA(cfg)
121 | }
122 | else {
123 | return getSPA(cfg)
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/app/page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/templates/app/store/mutations.js:
--------------------------------------------------------------------------------
1 | /*
2 | export function someMutation (state) {
3 | }
4 | */
5 |
--------------------------------------------------------------------------------
/templates/app/store/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | //
3 | }
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/electron/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/7c9d139e89a2ab4852fa9d9e7335f592252a1e9c/templates/electron/icons/icon.icns
--------------------------------------------------------------------------------
/templates/electron/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/7c9d139e89a2ab4852fa9d9e7335f592252a1e9c/templates/electron/icons/icon.ico
--------------------------------------------------------------------------------
/templates/electron/icons/linux-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quasarframework/quasar-cli/7c9d139e89a2ab4852fa9d9e7335f592252a1e9c/templates/electron/icons/linux-512x512.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/entry/client-entry.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 | <% if (supportIE) { %>
11 | import 'quasar-framework/dist/quasar.ie.polyfills.js'
12 | <% } %>
13 |
14 | <% extras && extras.filter(asset => asset).forEach(asset => { %>
15 | import 'quasar-extras/<%= asset %>/<%= asset %>.css'
16 | <% }) %>
17 |
18 | <% animations && animations.filter(asset => asset).forEach(asset => { %>
19 | import 'quasar-extras/animate/<%= asset %>.css'
20 | <% }) %>
21 |
22 | import 'quasar-app-styl'
23 |
24 | <% css && css.forEach(asset => { %>
25 | import '<%= asset %>'
26 | <% }) %>
27 |
28 | import Vue from 'vue'
29 | import createApp from './app.js'
30 |
31 | <% if (ctx.mode.pwa) { %>
32 | import 'app/<%= sourceFiles.registerServiceWorker %>'
33 | <% } %>
34 |
35 | <%
36 | const pluginNames = []
37 | if (plugins) {
38 | function hash (str) {
39 | const name = str.replace(/\W+/g, '')
40 | return name.charAt(0).toUpperCase() + name.slice(1)
41 | }
42 | plugins.filter(asset => asset.path !== 'boot' && asset.client !== false).forEach(asset => {
43 | let importName = 'p' + hash(asset.path)
44 | pluginNames.push(importName)
45 | %>
46 | import <%= importName %> from 'src/plugins/<%= asset.path %>'
47 | <% }) } %>
48 |
49 | <% if (preFetch) { %>
50 | import { addPreFetchHooks } from './client-prefetch.js'
51 | <% } %>
52 |
53 | <%
54 | const needsFastClick = ctx.mode.pwa || (ctx.mode.cordova && ctx.target.ios)
55 | if (needsFastClick) {
56 | %>
57 | import FastClick from 'fastclick'
58 | <% } %>
59 |
60 | <% if (ctx.mode.electron) { %>
61 | import electron from 'electron'
62 | Vue.prototype.$q.electron = electron
63 | <% } %>
64 |
65 | <%
66 | let hasBootPlugin = false
67 | if (!ctx.mode.ssr) {
68 | hasBootPlugin = plugins && plugins.find(asset => asset.path === 'boot')
69 |
70 | if (hasBootPlugin) { %>
71 | import boot from 'src/plugins/boot.js'
72 | <% } } %>
73 |
74 | <% if (ctx.dev) { %>
75 | Vue.config.devtools = true
76 | Vue.config.productionTip = false
77 | <% } %>
78 |
79 | <% if (ctx.dev) { %>
80 | console.info('[Quasar] Running <%= ctx.modeName.toUpperCase() + (ctx.mode.ssr && ctx.mode.pwa ? ' + PWA' : '') %> with <%= ctx.themeName.toUpperCase() %> theme.')
81 | <% if (ctx.mode.pwa) { %>console.info('[Quasar] Forcing PWA into the network-first approach to not break Hot Module Replacement while developing.')<% } %>
82 | <% } %>
83 |
84 | const { app, <%= store ? 'store, ' : '' %>router } = createApp()
85 |
86 | <% if (needsFastClick) { %>
87 | <% if (ctx.mode.pwa) { %>
88 | // Needed only for iOS PWAs
89 | if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream && window.navigator.standalone) {
90 | <% } %>
91 | document.addEventListener('DOMContentLoaded', () => {
92 | FastClick.attach(document.body)
93 | }, false)
94 | <% if (ctx.mode.pwa) { %>
95 | }
96 | <% } %>
97 | <% } %>
98 |
99 | <% if (pluginNames.length > 0) { %>
100 | ;[<%= pluginNames.join(',') %>].forEach(plugin => {
101 | plugin({
102 | app,
103 | router,
104 | <%= store ? 'store,' : '' %>
105 | Vue,
106 | ssrContext: null
107 | })
108 | })
109 | <% } %>
110 |
111 | <% if (ctx.mode.ssr) { %>
112 |
113 | // prime the store with server-initialized state.
114 | // the state is determined during SSR and inlined in the page markup.
115 | <% if (store) { %>
116 | if (window.__INITIAL_STATE__) {
117 | store.replaceState(window.__INITIAL_STATE__)
118 | }
119 | <% } %>
120 |
121 | const appInstance = new Vue(app)
122 |
123 | // wait until router has resolved all async before hooks
124 | // and async components...
125 | router.onReady(() => {
126 | <% if (preFetch) { %>
127 | addPreFetchHooks(router<%= store ? ', store' : '' %>)
128 | <% } %>
129 | appInstance.$mount('#q-app')
130 | })
131 |
132 | <% } else { // not SSR %>
133 |
134 | <% if (preFetch) { %>
135 | addPreFetchHooks(router<%= store ? ', store' : '' %>)
136 | <% } %>
137 |
138 | <% if (ctx.mode.cordova) { %>
139 | document.addEventListener('deviceready', () => {
140 | Vue.prototype.$q.cordova = window.cordova
141 | <% } %>
142 |
143 | <% if (hasBootPlugin) { %>
144 | boot({ app, router,<% if (store) { %> store,<% } %> Vue })
145 | <% } else { %>
146 | new Vue(app)
147 | <% } %>
148 |
149 | <% if (ctx.mode.cordova) { %>
150 | }, false) // on deviceready
151 | <% } %>
152 |
153 |
154 | <% } // end of Non SSR %>
155 |
--------------------------------------------------------------------------------
/templates/entry/client-prefetch.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 App from 'app/<%= sourceFiles.rootComponent %>'
11 |
12 | <% if (__loadingBar) { %>
13 | import { LoadingBar } from 'quasar'
14 | <% } %>
15 |
16 | <% if (!ctx.mode.ssr) { %>
17 | let appPrefetch = typeof App.preFetch === 'function'
18 | <% } %>
19 |
20 | function getMatchedComponents (to, router) {
21 | const route = to
22 | ? (to.matched ? to : router.resolve(to).route)
23 | : router.currentRoute
24 |
25 | if (!route) { return [] }
26 | return [].concat.apply([], route.matched.map(m => {
27 | return Object.keys(m.components).map(key => {
28 | return {
29 | path: m.path,
30 | c: m.components[key]
31 | }
32 | })
33 | }))
34 | }
35 |
36 | export function addPreFetchHooks (router<%= store ? ', store' : '' %>) {
37 | // Add router hook for handling preFetch.
38 | // Doing it after initial route is resolved so that we don't double-fetch
39 | // the data that we already have. Using router.beforeResolve() so that all
40 | // async components are resolved.
41 | router.beforeResolve((to, from, next) => {
42 | const
43 | matched = getMatchedComponents(to, router),
44 | prevMatched = getMatchedComponents(from, router)
45 |
46 | let diffed = false
47 | const components = matched
48 | .filter((m, i) => {
49 | return diffed || (diffed = (
50 | !prevMatched[i] ||
51 | prevMatched[i].c !== m.c ||
52 | m.path.indexOf('/:') > -1 // does it has params?
53 | ))
54 | })
55 | .filter(m => m.c && typeof m.c.preFetch === 'function')
56 | .map(m => m.c)
57 |
58 | <% if (!ctx.mode.ssr) { %>
59 | if (appPrefetch) {
60 | appPrefetch = false
61 | components.unshift(App)
62 | }
63 | <% } %>
64 |
65 | if (!components.length) { return next() }
66 |
67 | let routeUnchanged = true
68 | const redirect = url => {
69 | routeUnchanged = false
70 | next(url)
71 | }
72 | const proceed = () => {
73 | <% if (__loadingBar) { %>
74 | LoadingBar.stop()
75 | <% } %>
76 | if (routeUnchanged) { next() }
77 | }
78 |
79 | <% if (__loadingBar) { %>
80 | LoadingBar.start()
81 | <% } %>
82 |
83 | components
84 | .filter(c => c && c.preFetch)
85 | .reduce(
86 | (promise, c) => promise.then(() => routeUnchanged && c.preFetch({
87 | <% if (store) { %>store,<% } %>
88 | currentRoute: to,
89 | previousRoute: from,
90 | redirect
91 | })),
92 | Promise.resolve()
93 | )
94 | .then(proceed)
95 | .catch(e => {
96 | console.error(e)
97 | proceed()
98 | })
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/entry/server-entry.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 | <% extras && extras.filter(asset => asset).forEach(asset => { %>
11 | import 'quasar-extras/<%= asset %>/<%= asset %>.css'
12 | <% }) %>
13 |
14 | <% animations && animations.filter(asset => asset).forEach(asset => { %>
15 | import 'quasar-extras/animate/<%= asset %>.css'
16 | <% }) %>
17 |
18 | import 'quasar-app-styl'
19 |
20 | <% css && css.forEach(asset => { %>
21 | import '<%= asset %>'
22 | <% }) %>
23 |
24 | import createApp from './app.js'
25 | import Vue from 'vue'
26 | <% if (preFetch) { %>
27 | import App from 'app/<%= sourceFiles.rootComponent %>'
28 | <% } %>
29 |
30 | <%
31 | const pluginNames = []
32 | if (plugins) {
33 | function hash (str) {
34 | const name = str.replace(/\W+/g, '')
35 | return name.charAt(0).toUpperCase() + name.slice(1)
36 | }
37 | plugins.filter(asset => asset.path !== 'boot' && asset.server !== false).forEach(asset => {
38 | let importName = 'plugin' + hash(asset.path)
39 | pluginNames.push(importName)
40 | %>
41 | import <%= importName %> from 'src/plugins/<%= asset.path %>'
42 | <% }) } %>
43 |
44 | // This exported function will be called by `bundleRenderer`.
45 | // This is where we perform data-prefetching to determine the
46 | // state of our application before actually rendering it.
47 | // Since data fetching is async, this function is expected to
48 | // return a Promise that resolves to the app instance.
49 | export default context => {
50 | return new Promise(async (resolve, reject) => {
51 | const { app, <%= store ? 'store, ' : '' %>router } = createApp(context)
52 |
53 | <% if (pluginNames.length > 0) { %>
54 | ;[<%= pluginNames.join(',') %>].forEach(plugin => {
55 | plugin({
56 | app,
57 | router,
58 | <%= store ? 'store,' : '' %>
59 | Vue,
60 | ssrContext: context
61 | })
62 | })
63 | <% } %>
64 |
65 | const
66 | { url } = context,
67 | { fullPath } = router.resolve(url).route
68 |
69 | if (fullPath !== url) {
70 | return reject({ url: fullPath })
71 | }
72 |
73 | // set router's location
74 | router.push(url)
75 |
76 | // wait until router has resolved possible async hooks
77 | router.onReady(() => {
78 | const matchedComponents = router.getMatchedComponents()
79 | // no matched routes
80 | if (!matchedComponents.length) {
81 | return reject({ code: 404 })
82 | }
83 |
84 | <% if (preFetch) { %>
85 |
86 | let routeUnchanged = true
87 | const redirect = url => {
88 | routeUnchanged = false
89 | reject({ url })
90 | }
91 | App.preFetch && matchedComponents.unshift(App)
92 |
93 | // Call preFetch hooks on components matched by the route.
94 | // A preFetch hook dispatches a store action and returns a Promise,
95 | // which is resolved when the action is complete and store state has been
96 | // updated.
97 | matchedComponents
98 | .filter(c => c && c.preFetch)
99 | .reduce(
100 | (promise, c) => promise.then(() => routeUnchanged && c.preFetch({
101 | <% if (store) { %>store,<% } %>
102 | ssrContext: context,
103 | currentRoute: router.currentRoute,
104 | redirect
105 | })),
106 | Promise.resolve()
107 | )
108 | .then(() => {
109 | if (!routeUnchanged) { return }
110 |
111 | <% if (store) { %>context.state = store.state<% } %>
112 |
113 | <% if (__meta) { %>
114 | const App = new Vue(app)
115 | context.$getMetaHTML = App.$getMetaHTML(App)
116 | resolve(App)
117 | <% } else { %>
118 | resolve(new Vue(app))
119 | <% } %>
120 | })
121 | .catch(reject)
122 |
123 | <% } else { %>
124 |
125 | <% if (store) { %>context.state = store.state<% } %>
126 |
127 | <% if (__meta) { %>
128 | const App = new Vue(app)
129 | context.$getMetaHTML = App.$getMetaHTML(App)
130 | resolve(App)
131 | <% } else { %>
132 | resolve(new Vue(app))
133 | <% } %>
134 |
135 | <% } %>
136 | }, reject)
137 | })
138 | }
139 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------