@quasar/vite-plugin
Version:
Vite plugin for Quasar Framework
528 lines (438 loc) • 15.1 kB
JavaScript
;
var node_fs = require('node:fs');
var node_path = require('node:path');
var node_module = require('node:module');
var vite = require('vite');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
const quasarPath = node_path.dirname(require$1.resolve('quasar/package.json'));
const { version } = JSON.parse(
node_fs.readFileSync(node_path.join(quasarPath, 'package.json'), 'utf-8')
);
function getViteConfig (runMode, viteMode, externalViteCfg) {
const viteCfg = {
define: {
__QUASAR_VERSION__: `'${ version }'`,
__QUASAR_SSR__: false,
__QUASAR_SSR_SERVER__: false,
__QUASAR_SSR_CLIENT__: false
},
css: {
preprocessorOptions: {
// Use sass-embedded for better stability and performance
sass: {
api: 'modern-compiler',
silenceDeprecations: [ 'import', 'global-builtin' ]
},
scss: {
api: 'modern-compiler',
silenceDeprecations: [ 'import', 'global-builtin' ]
}
}
}
};
// Set this to the default value only if it's not already set.
// @quasar/app-vite configures this by itself when it needs it.
if (!externalViteCfg.define || externalViteCfg.define.__QUASAR_SSR_PWA__ === void 0) {
viteCfg.define.__QUASAR_SSR_PWA__ = false;
}
if (runMode === 'ssr-server') {
Object.assign(viteCfg.define, {
__QUASAR_SSR__: true,
__QUASAR_SSR_SERVER__: true
});
}
else {
// Alias "quasar" package to its dev file (which has flags)
// to reduce the number of HTTP requests while in DEV mode
if (viteMode !== 'production') {
viteCfg.resolve = {
alias: [
{ find: /^quasar$/, replacement: 'quasar/dist/quasar.client.js' }
]
};
}
else {
viteCfg.optimizeDeps = {
exclude: [ 'quasar' ]
};
}
if (runMode === 'ssr-client') {
Object.assign(viteCfg.define, {
__QUASAR_SSR__: true,
__QUASAR_SSR_CLIENT__: true
});
}
}
return viteCfg
}
let quasarImportMap;
function loadQuasarImportMap () {
if (quasarImportMap !== void 0) return
try {
quasarImportMap = JSON.parse(
node_fs.readFileSync(
node_path.join(quasarPath, 'dist/transforms/import-map.json'),
'utf-8'
)
);
}
catch (error) {
throw new Error('Failed to load Quasar import map', { cause: error })
}
}
const importQuasarRegex = /import\s*\{([\w,\s]+)\}\s*from\s*(['"])quasar\2;?/g;
function importTransformation (importName) {
const file = quasarImportMap[ importName ];
if (file === void 0) {
throw new Error('Unknown import from Quasar: ' + importName)
}
return 'quasar/' + file
}
/**
* Transforms JS code where importing from 'quasar' package
* Example:
* import { QBtn } from 'quasar'
* -> import QBtn from 'quasar/src/components/QBtn.js'
*/
function mapQuasarImports (code, importMap = {}) {
return code.replace(
importQuasarRegex,
(_, match) => match
.split(',')
.map(identifier => {
const data = identifier.split(' as ');
const importName = data[ 0 ].trim();
// might be an empty entry like below
// (notice useQuasar is followed by a comma)
// import { QTable, useQuasar, } from 'quasar'
if (importName === '') {
return ''
}
const importAs = data[ 1 ] !== void 0
? data[ 1 ].trim()
: importName;
importMap[ importName ] = importAs;
return `import ${ importAs } from '${ importTransformation(importName) }';`
})
.join('')
)
}
/**
* Transforms JS code where importing from 'quasar' package
* Example:
* import { QBtn } from 'quasar'
* -> add to importMap & importList & remove original import statement
* (removing original is required so that the end file will have
* only one import from Quasar statement)
*/
function removeQuasarImports (code, importMap, importSet, reverseMap) {
return code.replace(
importQuasarRegex,
(_, match) => {
match.split(',').forEach(identifier => {
const data = identifier.split(' as ');
const importName = data[ 0 ].trim();
// might be an empty entry like below
// (notice useQuasar is followed by a comma)
// import { QTable, useQuasar, } from 'quasar'
if (importName !== '') {
const importAs = data[ 1 ] !== void 0
? data[ 1 ].trim()
: importName;
importSet.add(importName + (importAs !== importName ? ` as ${ importAs }` : ''));
importMap[ importName ] = importAs;
reverseMap[ importName.replace(/-/g, '_') ] = importAs;
}
});
// we registered the original import and we
// remove this one to avoid duplicate imports from Quasar
return ''
}
)
}
let transformState;
function useTransformState () {
if (transformState !== void 0) {
return transformState
}
const autoImportData = JSON.parse(
node_fs.readFileSync(node_path.join(quasarPath, 'dist/transforms/auto-import.json'), 'utf-8')
);
const compRegex = {
kebab: new RegExp(`_resolveComponent\\("${ autoImportData.regex.kebabComponents }"\\)`, 'g'),
pascal: new RegExp(`_resolveComponent\\("${ autoImportData.regex.pascalComponents }"\\)`, 'g'),
combined: new RegExp(`_resolveComponent\\("${ autoImportData.regex.components }"\\)`, 'g')
};
const dirRegex = new RegExp(`_resolveDirective\\("${ autoImportData.regex.directives.replace(/v-/g, '') }"\\)`, 'g');
transformState = {
autoImportData,
compRegex,
dirRegex
};
return transformState
}
const lengthSortFn = (a, b) => b.length - a.length;
function vueTransform (content, autoImportComponentCase, useTreeshaking) {
const { autoImportData, compRegex, dirRegex } = useTransformState();
const importSet = new Set();
const importMap = {};
const compList = [];
const dirList = [];
const reverseMap = {};
const jsImportTransformed = useTreeshaking === true
? mapQuasarImports(content, importMap)
: removeQuasarImports(content, importMap, importSet, reverseMap);
let code = jsImportTransformed
.replace(compRegex[ autoImportComponentCase ], (_, match) => {
const name = autoImportData.importName[ match ];
const reverseName = match.replace(/-/g, '_');
if (importMap[ name ] === void 0) {
importSet.add(name);
reverseMap[ reverseName ] = name;
}
else {
reverseMap[ reverseName ] = importMap[ name ];
}
compList.push(reverseName);
return ''
})
.replace(dirRegex, (_, match) => {
const name = autoImportData.importName[ 'v-' + match ];
const reverseName = match.replace(/-/g, '_');
if (importMap[ name ] === void 0) {
importSet.add(name);
reverseMap[ reverseName ] = name;
}
else {
reverseMap[ reverseName ] = importMap[ name ];
}
dirList.push(reverseName);
return ''
});
if (compList.length !== 0) {
const list = compList.sort(lengthSortFn).join('|');
code = code
.replace(new RegExp(`const _component_(${ list }) = `, 'g'), '')
.replace(new RegExp(`_component_(${ list })`, 'g'), (_, match) => reverseMap[ match ]);
}
if (dirList.length !== 0) {
const list = dirList.sort(lengthSortFn).join('|');
code = code
.replace(new RegExp(`const _directive_(${ list }) = `, 'g'), '')
.replace(new RegExp(`_directive_(${ list })`, 'g'), (_, match) => reverseMap[ match ]);
}
if (importSet.size === 0) {
return code
}
const importList = [ ...importSet ];
const codePrefix = useTreeshaking === true
? importList.map(name => `import ${ name } from '${ importTransformation(name) }'`).join(';')
: `import {${ importList.join(',') }} from 'quasar'`;
return codePrefix + ';' + code
}
function createScssTransform (fileExtension, sassVariables) {
const sassImportCode = [ '@import \'quasar/src/css/variables.sass\'', '' ];
if (typeof sassVariables === 'string') {
sassImportCode.unshift(`@import '${ sassVariables }'`);
}
const prefix = fileExtension === 'sass'
? sassImportCode.join('\n')
: sassImportCode.join(';\n');
return content => {
const useIndex = Math.max(
content.lastIndexOf('@use '),
content.lastIndexOf('@forward ')
);
if (useIndex === -1) {
return prefix + content
}
const newLineIndex = content.indexOf('\n', useIndex);
if (newLineIndex !== -1) {
const index = newLineIndex + 1;
return content.substring(0, index) + prefix + content.substring(index)
}
return content + '\n' + prefix
}
}
/**
* @typedef {{
* extensionsList: string[];
* filenameRegex: RegExp;
* }} ExtMatcher
*
* @typedef {{
* template: (extMatcher: ExtMatcher) => boolean;
* script: (extMatcher: ExtMatcher) => boolean;
* style: (extMatcher: ExtMatcher) => boolean;
* }} ViteQueryIs
*/
/**
* @see https://github.com/vitejs/vite/blob/364aae13f0826169e8b1c5db41ac6b5bb2756958/packages/plugin-vue/src/utils/query.ts - source of inspiration
* @example
* '/absolute/path/src/App.vue' // Can contain combined template&script -> template: true, script: true, style: false
* '/absolute/path/src/App.vue?vue&type=script&lang.js' // Only contains script -> template: false, script: true, style: false
* '/absolute/path/src/App.vue?vue&type=style&lang.sass' // Only contains style -> template: false, script: false, style: true
* '/absolute/path/src/script.js' // Only contains script -> template: false, script: true, style: false
* '/absolute/path/src/index.scss' // Only contains style -> template: false, script: false, style: true
*
* @param {string} id
* @returns {{ filename: string; query: { [key: string]: string; }; is: ViteQueryIs }}
*/
function parseViteRequest (id) {
const [ filename, rawQuery ] = id.split('?', 2);
const query = Object.fromEntries(new URLSearchParams(rawQuery));
if (query.raw !== void 0) {
return {
// if it's a ?raw request, then don't touch it at all
template: () => false,
script: () => false,
style: () => false
}
}
return query.vue !== void 0 // is vue query?
? {
template: () => (
query.type === void 0
|| query.type === 'template'
// On prod, TS code turns into a separate 'script' request.
// See: https://github.com/vitejs/vite/pull/7909
|| (query.type === 'script' && (query[ 'lang.ts' ] !== void 0 || query[ 'lang.tsx' ] !== void 0))
),
script: matcher => (
(query.type === void 0 || query.type === 'script')
&& matcher.extensionsList.some(ext => query[ `lang.${ ext }` ] !== void 0) === true
),
style: matcher => (
query.type === 'style'
&& matcher.extensionsList.some(ext => query[ `lang.${ ext }` ] !== void 0) === true
)
}
: {
template: matcher => matcher.filenameRegex.test(filename),
script: matcher => matcher.filenameRegex.test(filename),
style: matcher => matcher.filenameRegex.test(filename)
}
}
function createExtMatcher (extensionsList) {
return {
extensionsList,
filenameRegex: new RegExp(`\\.(${ extensionsList.join('|') })$`)
}
}
const defaultOptions = {
runMode: 'web-client',
sassVariables: true,
devTreeshaking: false,
autoImportComponentCase: 'kebab',
autoImportVueExtensions: [ 'vue' ],
autoImportScriptExtensions: [ 'js', 'jsx', 'ts', 'tsx' ]
};
const defaultOptionsKeys = Object.keys(defaultOptions);
function parsePluginOptions (userOpts = {}) {
const opts = { ...userOpts };
for (const key of defaultOptionsKeys) {
if (opts[ key ] === void 0) {
opts[ key ] = defaultOptions[ key ];
}
}
return opts
}
function getConfigPlugin (opts) {
return {
name: 'vite:quasar:vite-conf',
configResolved (viteConf) {
const vueCfg = viteConf.plugins.find(entry => entry.name === 'vite:vue');
if (vueCfg === void 0) {
console.error('\n\n[Quasar] Error: In your Vite config file, please add the Quasar plugin ** after ** the Vue one\n\n');
process.exit(1);
}
},
config (viteConf, { mode }) {
return getViteConfig(opts.runMode, mode, viteConf)
}
}
}
const scssMatcher = createExtMatcher([ 'scss', 'module.scss' ]);
const sassMatcher = createExtMatcher([ 'sass', 'module.sass' ]);
function getScssTransformsPlugin (opts) {
const sassVariables = typeof opts.sassVariables === 'string'
? vite.normalizePath(opts.sassVariables)
: opts.sassVariables;
const scssTransform = createScssTransform('scss', sassVariables);
const sassTransform = createScssTransform('sass', sassVariables);
return {
name: 'vite:quasar:scss',
enforce: 'pre',
transform (src, id) {
const is = parseViteRequest(id);
if (is.style(scssMatcher) === true) {
return {
code: scssTransform(src),
map: null
}
}
if (is.style(sassMatcher) === true) {
return {
code: sassTransform(src),
map: null
}
}
return null
}
}
}
function getScriptTransformsPlugin (opts) {
let useTreeshaking = true;
const vueMatcher = createExtMatcher(opts.autoImportVueExtensions);
const scriptMatcher = createExtMatcher(opts.autoImportScriptExtensions);
return {
name: 'vite:quasar:script',
configResolved (resolvedConfig) {
if (opts.devTreeshaking === false && resolvedConfig.mode !== 'production') {
useTreeshaking = false;
}
else {
loadQuasarImportMap();
}
},
transform (src, id) {
const is = parseViteRequest(id);
if (is.template(vueMatcher) === true) {
return {
code: vueTransform(src, opts.autoImportComponentCase, useTreeshaking),
map: null // provide source map if available
}
}
if (useTreeshaking === true && is.script(scriptMatcher) === true) {
return {
code: mapQuasarImports(src),
map: null // provide source map if available
}
}
return null
}
}
}
function plugin (userOpts) {
const opts = parsePluginOptions(userOpts);
const plugins = [
getConfigPlugin(opts)
];
if (opts.sassVariables) {
plugins.push(
getScssTransformsPlugin(opts)
);
}
if (opts.runMode !== 'ssr-server') {
plugins.push(
getScriptTransformsPlugin(opts)
);
}
return plugins
}
const transformAssetUrls = JSON.parse(
node_fs.readFileSync(node_path.join(quasarPath, 'dist/transforms/loader-asset-urls.json'), 'utf-8')
);
exports.quasar = plugin;
exports.transformAssetUrls = transformAssetUrls;