Gustavo Lopes
Gustavo Lopes

Reputation: 4184

How can I eject quasar cli and use Vue + Quasar UMD?

I'm using Quasar for SPA only. But I don't need all the things that come with Quasar CLI.

Is there a way to eject Quasar CLI and start using Vue + Quasar UMD?

Basically, stop using quasar dev and start using vue serve?

Upvotes: 0

Views: 1707

Answers (2)

SaleCar
SaleCar

Reputation: 1196

If you want to use Quasar and Vue without CLI at all, you can check this starter project.
It's just Quasar UMD (ver.2) and vanilla Vue (ver.3).

Github: https://github.com/SaleCar/Quasar-UMD-Template
Demo: http://quasar.rf.gd/

Upvotes: 1

IVO GELOV
IVO GELOV

Reputation: 14269

Yes, you can. The following changes should be made:

// vue.config.js
const QuasarLoader = require('./quasar-loader/index');

module.exports =
{
  css:
  {
    loaderOptions:
    {
      scss:
        {
          prependData: `@import "~quasar/src/css/variables.sass";`
        }
    }
  },
  transpileDependencies: ['quasar'],
  configureWebpack: (config) => 
  {
    if (!config.plugins) config.plugins = [];
    config.plugins.push(new QuasarLoader());
  },

}
// quasar-loader/auto-import.js

/**
 * Quasar runtime for auto-importing
 * components or directives.
 *
 * Warning! This file does NOT get transpiled by Babel
 * but is included into the UI code.
 *
 * @param component {Vue} Component object
 * @param type      {String} One of 'components' or 'directives'
 * @param items     {Object} Object containing components or directives
 */
module.exports = function quasarLoader(component, type, items)
{
  /* we use a workaround in functional templates
    <template>
      <component :is="$options.components.QBtn" ... />
    </template>
   */
  var target, i;
  var opt = component.options;

  if (!opt[type])
  {
    opt[type] = items
  }
  else
  {
    target = opt[type];
    for (i in items)
    {
      if (!target[i])
      {
        target[i] = items[i]
      }
    }
  }
}; 
// quasar-loader/index.js

const RuleSet = require('webpack/lib/RuleSet');
let vueLoaderPath;

try
{
  vueLoaderPath = require.resolve('vue-loader');
}
catch (err)
{}

function isVueLoader(use)
{
  return use.ident === 'vue-loader-options' ||
    use.loader === 'vue-loader' ||
    (vueLoaderPath && use.loader === vueLoaderPath)
}

class QuasarLoaderPlugin
{
  constructor(options)
  {
    this.options = options || {}
  }

  apply(compiler)
  {
    // use webpack's RuleSet utility to normalize user rules
    const rawRules = compiler.options.module.rules;
    const { rules } = new RuleSet(rawRules);

    // find the rule that applies to vue files
    const vueRuleIndex = rules.findIndex(rule => rule.use && rule.use.find(isVueLoader));
    const vueRule = rules[vueRuleIndex];

    if (!vueRule)
    {
      throw new Error(
        `[QuasarLoaderPlugin Error] No matching rule for vue-loader found.\n` +
        `Make sure there is at least one root-level rule that uses vue-loader.`
      )
    }

    vueRule.use.unshift({
      loader: require.resolve('./loader'),
      options: this.options || { nameCase: 'kebab' }
    });

    compiler.options.module.rules = rules;
  }
}

module.exports = QuasarLoaderPlugin; 
// quasar-loader/loader.js

const path = require('path');
const compiler = require('vue-template-compiler');
// const loaderOptions = require('loader-utils/lib/getOptions');
const stringifyRequest = require('loader-utils/lib/stringifyRequest');

const importData = require('quasar/dist/babel-transforms/auto-import.json');
const importTransform = require('quasar/dist/babel-transforms/imports.js');
const runtimePath = require.resolve('./auto-import.js');

// regex to match functional components
const funcCompRegex = new RegExp(
  'var\\s+component\\s*=\\s*normalizer\\((?:[^,]+,){3}\\s*true,'
);

function transform(itemArray)
{
  return itemArray
    .map(name => `import ${name} from '${importTransform(name)}'`)
    .join(`\n`)
}

module.exports = async function (content, sourceMap)
{
  this.async();
  this.cacheable();

  if (!this.resourceQuery)
  {
    const readFile = path => new Promise((resolve, reject) =>
    {
      this.fs.readFile(path, function (err, data)
      {
        if (err) reject(err);
        else resolve(data)
      })
    });

    this.addDependency(this.resourcePath);

    const tags = new Set();
    const directives = new Set();
    const file = (await readFile(this.resourcePath)).toString('utf8');
    const component = compiler.parseComponent(file);

    if (component.template)
    {
      if (component.template.src)
      {
        const externalFile = (await new Promise((resolve, reject) =>
          this.resolve(path.dirname(this.resourcePath), component.template.src, (err, result) =>
          {
            if (err) reject(err);
            else resolve(result)
          })
        ));
        component.template.content = (await readFile(externalFile)).toString('utf8'); // external content
      }

      const compRegexKebab = new RegExp('^' + importData.regex.kebabComponents + '$');
      const compRegexPascal = new RegExp('^' + importData.regex.pascalComponents + '$');
      const dirRegex = new RegExp('^' + importData.regex.directives + '$');

      compiler.compile(component.template.content, {
        modules: [{
          postTransformNode: node =>
          {
            if ("directives" in node)
            {
              node.directives.forEach(({ name }) =>
              {
                if (dirRegex.test('v-' + name))
                {
                  directives.add(importData.importName['v-' + name]);
                }
              });
            }
            if (compRegexKebab.test(node.tag))
            {
              tags.add(importData.importName[node.tag]);
            }
            else if (compRegexPascal.test(node.tag))
            {
              tags.add(node.tag);
            }
          }
        }]
      });

      const arrTags = [...tags];
      const arrDirs = [...directives];

      if (arrTags.length > 0 || arrDirs.length > 0)
      {
        const functional = funcCompRegex.test(content);
        let newContent = '/* quasar-loader */\n';
        newContent += `import qInstall from ${stringifyRequest(this, runtimePath)}\n`;
        if (arrTags.length > 0)
        {
          if (functional) console.error('Using workaround for local Vue components (' + arrTags.join(',') + ') in a functional template.');
          newContent += transform(arrTags) + '\n';
          newContent += `qInstall(component, "components", {${arrTags.join(",")}})\n`;
        }
        if (arrDirs.length > 0)
        {
          if (functional) console.error('Using local Vue directive (' + arrDirs.join(',') + ') in a functional component is not supported.');
          newContent += transform(arrDirs) + '\n';
          newContent += `qInstall(component, "directives", {${arrDirs.join(",")}})\n`;
        }

        // Insert our modification before the HMR code
        const hotReload = content.indexOf('/* hot reload */');
        if (hotReload > -1)
        {
          content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
        }
        else
        {
          content += '\n\n' + newContent
        }
      }
    }
  }

  this.callback(null, content, sourceMap);
}; 
// src/main.js

import QuasarInstall from 'quasar/src/vue-plugin'
import iconSet from 'quasar/icon-set/svg-mdi-v5'
// import QuasarDialog from 'quasar/src/plugins/Dialog'
import '@/assets/scss/quasar.scss'

QuasarInstall.install(Vue, {
  iconSet,
  plugins:
    {
      // Dialog: QuasarDialog,
    },
});
// src/assets/scss/quasar.scss

$primary   : #409EFF;
$secondary : #26A69A;
$accent    : #9C27B0;

$dark      : #1D1D1D;

$positive  : #20A835;
$negative  : #F04025;
$info      : #1975D0;
$warning   : #F2C037;

@import '~quasar/src/css/index.sass';  

Upvotes: 1

Related Questions