Commit a0769a68 authored by Ejez's avatar Ejez

feat(quasar-cli): Add MPA support

Add necessary webpack configs to support multi-page apps development.
parent c88010b3
......@@ -50,7 +50,7 @@ module.exports = {
'prefer-promise-reject-errors': 'off',
// allow console.log during development only
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
......
......@@ -20,3 +20,5 @@ yarn-error.log*
*.ntvs*
*.njsproj
*.sln
params.js
\ No newline at end of file
// html/pug files imported here will be processed and bundled by webpack
import '@app/shared/layout/layout.pug?external'
import '@app/pages/main/html_files'
module.exports = {
main_page: {
entry: `${__dirname}/pages/main/main_page.js`
}
}
import './final.pug?external'
import './page.pug?external'
import Vue from 'vue'
import '@app/shared'
import Layout from '@app/shared/layout/layout'
new Vue({
delimiters: ['[[', ']]'],
data () {
return Layout.data()
}
}).$mount('#q-app')
| {% extends "pages/main/main_page.wp.html" %}
| {% load wagtailcore_tags %}
| {% block page %}
| {% for block in page.body %}
section.text-body1 {% include_block block %}
| {% endfor %}
| {% endblock %}
import './quasar/quasar'
if (process.env.PROD && process.env.MODE === 'pwa') {
import('app/src-pwa/register-service-worker.js')
}
// Needed only for iOS PWAs
if (
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
!window.MSStream &&
window.navigator.standalone
) {
import(/* webpackChunkName: "fastclick" */ '@quasar/fastclick')
}
export default {
data () {
return {
leftDrawerOpen: false
}
}
}
| {% extends "base.html" %}
| {% block layout %}
div(id="q-app" v-cloak)
q-layout.bg-white(view="lHh Lpr lFf")
q-header(elevated)
q-toolbar
q-btn(flat dense round @click="leftDrawerOpen = !leftDrawerOpen" aria-label="Menu" icon="menu")
q-toolbar-title {% block toolbar-title %}{% endblock %}
q-drawer(v-model="leftDrawerOpen" show-if-above bordered content-class="bg-grey-2")
q-list
q-item-label(header) Essential Links
q-item(clickable tag="a" target="_blank" rel="noopener" href="http://quasar.dev")
q-item-section(avatar)
q-icon(name="school")
q-item-section
q-item-label Docs
q-item-label(caption) https://quasar.dev
q-item(clickable tag="a" target="_blank" rel="noopener" href="https://github.quasar.dev")
q-item-section(avatar)
q-icon(name="code")
q-item-section
q-item-label Github
q-item-label(caption) github.com/quasarframework
q-item(clickable tag="a" target="_blank" rel="noopener" href="http://chat.quasar.dev")
q-item-section(avatar)
q-icon(name="chat")
q-item-section
q-item-label Discord Chat Channel
q-item-label(caption) https://chat.quasar.dev
q-item(clickable tag="a" target="_blank" rel="noopener" href="https://forum.quasar.dev")
q-item-section(avatar)
q-icon(name="record_voice_over")
q-item-section
q-item-label Forum
q-item-label(caption) https://forum.quasar.dev
q-item(clickable tag="a" target="_blank" rel="noopener" href="https://twitter.quasar.dev")
q-item-section(avatar)
q-icon(name="rss_feed")
q-item-section
q-item-label Twitter
q-item-label(caption) @quasarframework
q-item(clickable tag="a" target="_blank" rel="noopener" href="https://facebook.quasar.dev")
q-item-section(avatar)
q-icon(name="public")
q-item-section
q-item-label Facebook
q-item-label(caption) @QuasarFramework
q-page-container
q-page(padding) {% block page %}{% endblock %}
| {% endblock %}
import '@quasar/extras/roboto-font/roboto-font.css'
import '@quasar/extras/material-icons/material-icons.css'
// We load Quasar stylesheet file
import 'quasar/dist/quasar.sass'
import Vue from 'vue'
import {
Quasar,
QLayout,
QHeader,
QDrawer,
QPageContainer,
QPage,
QToolbar,
QToolbarTitle,
QBtn,
QIcon,
QList,
QItem,
QItemSection,
QItemLabel
} from 'quasar'
Vue.use(Quasar, {
config: {},
components: {
QLayout,
QHeader,
QDrawer,
QPageContainer,
QPage,
QToolbar,
QToolbarTitle,
QBtn,
QIcon,
QList,
QItem,
QItemSection,
QItemLabel
},
directives: {},
plugins: {}
})
{% extends "shared/layout/layout.wp.html" %}
{% load static %}
{% block html %}
<!DOCTYPE html>
<html {% block html_tag_attributes %}{{ block.super }}{% endblock %}>
<head>
{% block head %}
{{ block.super }}
<meta http-equiv=X-UA-Compatible content="IE=edge">
{% endblock %}
</head>
<body {% block body_tag_attributes %}{{ block.super }}{% endblock %}>
{% block body %}{{ block.super }}{% endblock %}
</body>
</html>
{% endblock %}
| {% extends "shared/layout/layout.wp.html" %}
| {% load static %}
| {% block html %}
doctype html
html({% block html_tag_attributes %}{{ block.super }}{% endblock %})
head
| {% block head %}
| {{ block.super }}
meta(http-equiv="X-UA-Compatible" content="IE=edge")
| {% endblock %}
body({% block body_tag_attributes %}{{ block.super }}{% endblock %})
| {% block body %}{{ block.super }}{% endblock %}
| {% endblock %}
This diff is collapsed.
......@@ -21,9 +21,15 @@
"@quasar/app": "^1.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"django-static-webpack-plugin": "^0.1.5",
"eslint": "^5.10.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-vue": "^5.0.0"
"eslint-plugin-vue": "^5.0.0",
"extract-loader": "^3.1.0",
"html-loader": "^0.5.5",
"pug": "^2.0.4",
"pug-plain-loader": "^1.0.0",
"raw-loader": "^3.1.0"
},
"engines": {
"node": ">= 8.9.0",
......
let params = {
appsOutputDir: '/apps/dev/example/example/static/bundles',
app: 'quasar-classic-django-example',
djangoStaticTags: true,
// server of your web project, ex: django-dev-server
// requests for non webpack bundles will be proxied to it
devServerProxyTarget: 'http://localhost:8000'
}
params.distDir = `${params.appsOutputDir}/${params.app}`
params.publicPath = `/static/bundles/${params.app}/`
// HtmlWebpackPlugin default template
params.indexHtmlTemplate = 'apps/shared/webpack-html-templates/index.pug'
module.exports = params
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
const HtmlWebpackPlugin = require('@quasar/app/node_modules/html-webpack-plugin')
const AutoChunksWebpackPlugin = require('./webpack-plugins/autochunks')
const path = require('path')
const params = require('./params')
const pages = require(`./apps/${params.app}/pages`)
module.exports = function (ctx) {
return {
// app boot file (/src/boot)
......@@ -74,11 +80,146 @@ module.exports = function (ctx) {
formatter: require('eslint').CLIEngine.getFormatter('stylish')
}
})
}
cfg.resolve.alias = {
...cfg.resolve.alias,
'@apps': `${__dirname}/apps`,
'@app': `${__dirname}/apps/${params.app}`
}
// Rule for loading html files with '?external' resourceQuery.
// Files will be processed and output as separate files in the output directory.
cfg.module.rules.push({
test: /\.html$/,
resourceQuery: /external/,
use: [
{
loader: 'file-loader',
options: {
name: 'html/[path][name].wp.html',
context: `apps/${params.app}`
}
},
{ loader: 'extract-loader' },
{ loader: 'html-loader' }
]
})
// Rules for loading pug files
cfg.module.rules.push({
test: /\.pug$/,
oneOf: [
{
resourceQuery: /vue/,
use: [{ loader: 'pug-plain-loader' }]
},
{
// Files with '?external' resourceQuery will be processed and output as separate files.
resourceQuery: /external/,
exclude: [/webpack-html-templates/],
use: [
{
loader: 'file-loader',
options: {
name: 'html/[path][name].wp.html',
context: `apps/${params.app}`
}
},
{ loader: 'extract-loader' },
{ loader: 'html-loader' },
{ loader: 'pug-plain-loader' }
]
},
{
use: [{ loader: 'raw-loader' }, { loader: 'pug-plain-loader' }]
}
]
})
// Add entry for html/pug files
// Use 'html_files.js' to import html files that you want to be output
// as separate files, ex: import './layout.pug?external
cfg.entry.html_files = `${__dirname}/apps/${params.app}/html_files.js`
Object.keys(pages).forEach(function (page) {
// Add page webpack entry
cfg.entry[page] = pages[page].entry
// HtmlWebpackPlugin default output file
let defaultPageFilename =
'html/' +
path.relative(
`${__dirname}/apps/${params.app}`,
path.dirname(pages[page].entry)
) +
`/${page}.wp.html`
// if page template and filename are not provided, use defaults
pages[page].template = pages[page].template
? pages[page].template
: params.indexHtmlTemplate
pages[page].filename = pages[page].filename
? pages[page].filename
: defaultPageFilename
// Output an html file for the page with all necessary assets injected
cfg.plugins.push(
new HtmlWebpackPlugin({
template: pages[page].template,
filename: pages[page].filename,
chunks: [page]
})
)
})
// Auto add necessary chunks to HtmlWebpackPlugin generated files
cfg.plugins.push(
new AutoChunksWebpackPlugin()
)
// Transform links in HtmlWebpackPlugin generated html files to django format
// ex: {% static 'bundles/myapp/js/app.4ddb1544.js' %}
if (params.djangoStaticTags) {
const DjangoStaticWebpackPlugin = require('django-static-webpack-plugin')
cfg.plugins.push(
new DjangoStaticWebpackPlugin(`bundles/${params.app}`)
)
}
},
chainWebpack (chain) {
// Restrict chunks in 'index.html'
chain.plugin('html-webpack').tap(args => {
args[0].chunks = ['app']
return args
})
chain.output
.path(params.distDir)
.publicPath(params.publicPath)
},
distDir: params.distDir,
publicPath: params.publicPath,
vueCompiler: true
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
devServer: {
publicPath: params.publicPath,
// Proxy requests for non webpack assets
proxy: [
{
context: ['!' + params.publicPath + '**'],
target: params.devServerProxyTarget
}
],
// Write html files to disk as they might be needed by other servers
writeToDisk: filePath => {
return /\.html$/.test(filePath)
},
// https: true,
// port: 8080,
open: true // opens browser window automatically
......
/*
* This file (which will be your service worker)
* is picked up by the build system ONLY if
* quasar.conf > pwa > workboxPluginMode is set to "InjectManifest"
*/
import { register } from 'register-service-worker'
// The ready(), registered(), cached(), updatefound() and updated()
// events passes a ServiceWorkerRegistration instance in their arguments.
// ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
register(process.env.SERVICE_WORKER_FILE, {
// The registrationOptions object will be passed as the second argument
// to ServiceWorkerContainer.register()
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameter
// registrationOptions: { scope: './' },
ready () {
if (process.env.DEV) {
console.log('App is being served from cache by a service worker.')
}
},
registered (/* registration */) {
if (process.env.DEV) {
console.log('Service worker has been registered.')
}
},
cached (/* registration */) {
if (process.env.DEV) {
console.log('Content has been cached for offline use.')
}
},
updatefound (/* registration */) {
if (process.env.DEV) {
console.log('New content is downloading.')
}
},
updated (/* registration */) {
if (process.env.DEV) {
console.log('New content is available; please refresh.')
}
},
offline () {
if (process.env.DEV) {
console.log('No internet connection found. App is running in offline mode.')
}
},
error (err) {
if (process.env.DEV) {
console.error('Error during service worker registration:', err)
}
}
})
// A plugin that finds automatically the needed chunks to include in an HTMLWebpackPlugin instance
class AutoChunksWebpackPlugin {
apply (compiler) {
compiler.hooks.compilation.tap('AutoChunksWebpackPlugin', compilation => {
compilation.hooks.htmlWebpackPluginAlterChunks.tap(
'AutoChunksWebpackPlugin',
(_, { plugin }) => {
const mainChunk = plugin.options.chunks[0]
const entrypoint = compilation.entrypoints.get(mainChunk)
return entrypoint.chunks.map(chunk =>
({
names: chunk.name ? [chunk.name] : [],
files: chunk.files.slice(),
size: chunk.modulesSize(),
hash: chunk.hash
})
)
}
)
})
}
}
module.exports = AutoChunksWebpackPlugin
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment